diff --git a/.babelrc.js b/.babelrc.js index bece57ec4a5..74d26caf0fd 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -16,12 +16,12 @@ module.exports = { { "targets": { "browsers": [ - "chrome >= 61", - "safari >=8", - "edge >= 14", - "ff >= 57", - "ie >= 10", - "ios >= 8" + "chrome >= 75", + "safari >=10", + "edge >= 70", + "ff >= 70", + "ie >= 11", + "ios >= 11" ] } } diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ea5e87218c..ea5fb916a91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ aliases: docker: # specify the version you desire here - image: circleci/node:12.16.1 - + resource_class: xlarge # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ @@ -94,4 +94,4 @@ workflows: - e2etest experimental: - pipelines: true \ No newline at end of file + pipelines: true diff --git a/.eslintrc.js b/.eslintrc.js index c64ab379c52..78e4fb1bb33 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,50 +1,53 @@ -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" + 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]] } })) }; diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..a3246cffd6d --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,28 @@ + +name-template: 'Prebid $RESOLVED_VERSION Release' +tag-template: '$RESOLVED_VERSION' +categories: + - title: '🚀 New Features' + label: 'feature' + - title: '🛠 Maintenance' + label: 'maintenance' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' +change-template: '- $TITLE (#$NUMBER)' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: minor +template: | + ## In This Release + $CHANGES diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000000..8152b61275d --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,18 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 016f4055216..606d26cd25a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,11 @@ Contributions are always welcome. To contribute, [fork](https://help.github.com/ commit your changes, and [open a pull request](https://help.github.com/articles/using-pull-requests/) against the master branch. -Pull requests must have 80% code coverage before beign considered for merge. +Pull requests must have 80% code coverage before being considered for merge. Additional details about the process can be found [here](./PR_REVIEW.md). +There are more details available if you'd like to contribute a [bid adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html) or [analytics adapter](https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). + ## Issues [prebid.org](http://prebid.org/) contains documentation that may help answer questions you have about using Prebid.js. If you can't find the answer there, try searching for a similar issue on the [issues page](https://github.com/prebid/Prebid.js/issues). @@ -57,7 +59,7 @@ When you are adding code to Prebid.js, or modifying code that isn't covered by a Prebid.js already has many tests. Read them to see how Prebid.js is tested, and for inspiration: - Look in `test/spec` and its subdirectories -- Tests for bidder adaptors are located in `test/spec/modules` +- Tests for bidder adapters are located in `test/spec/modules` A test module might have the following general structure: diff --git a/PR_REVIEW.md b/PR_REVIEW.md index dac50593d6e..1152e2942bf 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -1,20 +1,62 @@ ## 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. For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. +### Running Tests and Verifying Integrations + +General gulp commands include separate commands for serving the codebase on a built in webserver, creating code coverage reports and allowing serving integration examples. The `review-start` gulp command combinese those into one command. + +- 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 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 examples can be edited and used as needed to verify functionality + ### General PR review Process -- Checkout the branch (these instructions are available on the github PR page as well). + +- 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). - 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 has no unit test coverage, ask for unit tests to be included in the PR. +- 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` - Verify no code quality violations are present from linting (should be reported in terminal) +- Make sure the code is not setting cookies or localstorage directly -- it must use the `StorageManager`. - Review for obvious errors or bad coding practice / use best judgement here. - If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change. - 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. - - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/bidder.md file): +- 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. + - 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 + +### Reviewing a New or Updated Bid Adapter + +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: + - Lower case alphanumeric with the only special character allowed is underscore. + - The bidder code should be unique for the first 6 characters + - Reserved words that cannot be used as bidder names: all, context, data, general, prebid, and skadn +- Verify that bidder has submitted valid bid params and that bids are being received. +- Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. +- Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. +- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. +- All bidder parameter conventions must be followed: + - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. + - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd). + - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function. + - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. + - The bidRequest page referrer must checked in addition to any bidder-specific parameter. + - If they're getting the COPPA flag, it must come from config.getConfig('coppa'); +- Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` @@ -23,35 +65,67 @@ For modules and core platform updates, the initial reviewer should request an ad - If they support COPPA, add `coppa_supported: true` - If they support SChain, add `schain_supported: true` - If their bidder doesn't work well with safeframed creatives, add `safeframes_ok: false`. This will alert publishers to not use safeframed creatives when creating the ad server entries for their bidder. - - If they're a member of Prebid.org, add `prebid_member: true` -- If all above is good, add a `LGTM` comment and request 1 additional core member to review. -- Once there is 2 `LGTM` on the PR, merge to master -- Ask the submitter to add a PR for documentation if applicable. -- Add a line into the [draft release](https://github.com/prebid/Prebid.js/releases) notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) -- Add the PR to the appropriate project board (I.E. 1.23.0 Release) for the week, [see](https://github.com/prebid/Prebid.js/projects) - -### New Adapter or updates to adapter process -- Follow steps above for general review process. In addition, please verify the following: -- Verify that bidder has submitted valid bid params and that bids are being received. -- Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. -- Verify that the bidder is being as efficient as possible, ideally not loading an external library, however if they do load a library it should be cached. -- Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. -- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. -- If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core. -- Requests to the bidder should support HTTPS -- Responses from the bidder should be compressed (such as gzip, compress, deflate) -- Bid responses may not use JSONP: All requests must be AJAX with JSON responses -- All user-sync (aka pixel) activity must be registered via the provided functions -- Adapters may not use the $$PREBID_GLOBAL$$ variable -- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. -- Adapters may not globally override or default the standard ad server targeting values: hb_adid, hb_bidder, hb_pb, hb_deal, or hb_size, hb_source, hb_format. + - If they're setting a deal ID in some scenarios, add `bidder_supports_deals: true` + - If they have an IAB Global Vendor List ID, add `gvl_id: ID`. There's no default. - 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: 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: 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. +- the filename should be camel case ending with `IdSystem` (e.g. `myCompanyIdSystem.js`) +- the `const MODULE_NAME` value should be camel case ending with `Id` (e.g. `myCompanyId` ) +- the response of the `decode` method should be an object with the key being ideally camel case similar to the module name and ending in `id` or `Id`, but in some cases this value is a shortened name and sometimes with the `id` part being all lowercase, provided there are no other uppercase letters. if there's no id or it's an invalid object, the response should be `undefined`. example "valid" values (although this is more style than a requirement) + - `mcid` + - `mcId` + - `myCompanyId` +- make sure they've added references of their new module everywhere required: + - modules/.submodules.json + - modules/userId/eids.js + - modules/userId/eids.md + - modules/userId/userId.md +- tests can go either within the userId_spec.js file or in their own _spec file if they wish +- GVLID is recommended in the *IdSystem file if they operate in EU +- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples) +- the 3 available methods (getId, extendId, decode) should be used as they were intended + - decode (required method) should not be making requests to retrieve a new ID, it should just be decoding a response + - extendId (optional method) should not be making requests to retrieve a new ID, it should just be adding additional data to the id object + - getId (required method) should be the only method that gets a new ID (from ajax calls or a cookie/local storage). this ensures that decode and extend do not unnecessarily delay the auction in places where it is not expected. +- in the eids.js file, the source should be the actual domain of the provider, not a made up domain. +- in the eids.js file, the key in the array should be the same value as the key in the decode function +- make sure all supported config params align in the submodule js file and the docs / examples +- make sure there's a docs pull request + +### Reviewing a New or Updated Real-Time-Data Sub-Module + +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 + - is not loading external code. If it is, escalate to the #prebid-js Slack channel. + - is reading `config` from the function signature rather than calling `getConfig`. + - is sending data to the bid request only as either First Party Data or in bidRequest.rtd.RTDPROVIDERCODE. + - is making HTTPS requests as early as possible, but not more often than needed. + - doesn't force bid adapters to load additional code. +- Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. +- Make sure there's a docs pull request + ## 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 0dd5b25a50f..be16e9e3547 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) @@ -14,6 +13,8 @@ This README is for developers who want to contribute to Prebid.js. Additional documentation can be found at [the Prebid homepage](http://prebid.org). Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html). +Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate. + **Table of Contents** - [Usage](#Usage) @@ -91,12 +92,12 @@ Or for Babel 6: Then you can use Prebid.js as any other npm depedendency ```javascript -import prebid from 'prebid.js'; +import pbjs from 'prebid.js'; import 'prebid.js/modules/rubiconBidAdapter'; // imported modules will register themselves automatically with prebid import 'prebid.js/modules/appnexusBidAdapter'; -prebid.processQueue(); // required to process existing pbjs.queue blocks and setup any further pbjs.queue execution +pbjs.processQueue(); // required to process existing pbjs.queue blocks and setup any further pbjs.queue execution -prebid.requestBids({ +pbjs.requestBids({ ... }) @@ -110,11 +111,11 @@ prebid.requestBids({ $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js - $ npm install + $ npm ci *Note:* You need to have `NodeJS` 12.16.1 or greater installed. -*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm install`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. +*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm ci`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. You can check if this is installed by running `gulp -v` and seeing the version that's listed in the `CLI` field of the output. If you have the `gulp` package installed globally, it's likely the same version that you'll see in the `Local` field. If you already have `gulp-cli` installed, it should be a lower major version (it's at version `2.0.1` at the time of the transition). @@ -140,7 +141,7 @@ This runs some code quality checks, starts a web server at `http://localhost:999 ### Build Optimization -The standard build output contains all the available modules from within the `modules` folder. +The standard build output contains all the available modules from within the `modules` folder. Note, however that there are bid adapters which support multiple bidders through aliases, so if you don't see a file in modules for a bid adapter, you may need to grep the repository to find the name of the module you need to include. You might want to exclude some/most of them from the final bundle. To make sure the build only includes the modules you want, you can specify the modules to be included with the `--modules` CLI argument. @@ -150,7 +151,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 @@ -200,6 +201,11 @@ To run the unit tests: gulp test ``` +To run the unit tests for a particular file (example for pubmaticBidAdapter_spec.js): +```bash +gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" +``` + To generate and view the code coverage reports: ```bash @@ -258,7 +264,7 @@ directory you will have sourcemaps available in your browser's developer tools. To run the example file, go to: -+ `http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html` ++ `http://localhost:9999/integrationExamples/gpt/hello_world.html` As you make code changes, the bundles will be rebuilt and the page reloaded automatically. @@ -266,7 +272,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. [60+ 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/src/adapters) are supported by Prebid.js. For guidelines, see [Contributing](./CONTRIBUTING.md). @@ -274,9 +280,7 @@ Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tr ### Add a Bidder Adapter -To add a bidder adapter module, see the instructions in [How to add a bidder adaptor](http://prebid.org/dev-docs/bidder-adaptor.html). - -Please **do NOT load Prebid.js inside your adapter**. If you do this, we will reject or remove your adapter as appropriate. +To add a bidder adapter module, see the instructions in [How to add a bidder adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html). ### Code Quality diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index 7b2c6244bd7..bfbd0772c3e 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -1,6 +1,14 @@ **Table of Contents** - [Release Schedule](#release-schedule) - [Release Process](#release-process) + - [1. Make sure that all PRs have been named and labeled properly per the PR Process](#1-make-sure-that-all-prs-have-been-named-and-labeled-properly-per-the-pr-process) + - [2. Make sure all browserstack tests are passing](#2-make-sure-all-browserstack-tests-are-passing) + - [3. Prepare Prebid Code](#3-prepare-prebid-code) + - [4. Verify the Release](#4-verify-the-release) + - [5. Create a GitHub release](#5-create-a-github-release) + - [6. Update coveralls _(skip for legacy)_](#6-update-coveralls-skip-for-legacy) + - [7. Distribute the code](#7-distribute-the-code) + - [8. Increment Version for Next Release](#8-increment-version-for-next-release) - [Beta Releases](#beta-releases) - [FAQs](#faqs) @@ -9,7 +17,7 @@ We aim to push a new release of Prebid.js every week on Tuesday. While the releases will be available immediately for those using direct Git access, -it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. +it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) @@ -19,14 +27,20 @@ Announcements regarding releases will be made to the #headerbidding-dev channel _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for your repo, all of the following git commands will have to be modified to reference the proper remote (e.g. `upstream`)_ -1. Make Sure all browserstack tests are passing. On PR merge to master CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results. - - In case of failure do following, +### 1. Make sure that all PRs have been named and labeled properly per the [PR Process](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md#general-pr-review-process) + * Do this by checking the latest draft release from the [releases page](https://github.com/prebid/Prebid.js/releases) and make sure nothing appears in the first section called "In This Release". If they do, please open the PRs and add the appropriate labels. + * Do a quick check that all the titles/descriptions look ok, and if not, adjust the PR title. + +### 2. Make sure all browserstack tests are passing + + On PR merge to master, CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results.** + + In case of failure do following, - Try to fix the failing tests. - If you are not able to fix tests in time. Skip the test, create issue and tag contributor. - #### How to run tests in browserstack - + **How to run tests in browserstack** + _Note: the following browserstack information is only relevant for debugging purposes, if you will not be debugging then it can be skipped._ Set the environment variables. You may want to add these to your `~/.bashrc` for convenience. @@ -35,40 +49,40 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for export BROWSERSTACK_USERNAME="my browserstack username" export BROWSERSTACK_ACCESS_KEY="my browserstack access key" ``` - + ``` gulp test --browserstack >> prebid_test.log - + vim prebid_test.log // Will show the test results ``` -2. Prepare Prebid Code +### 3. Prepare Prebid Code Update the package.json version to become the current release. Then commit your changes. ``` - git commit -m "Prebid 1.x.x Release" + git commit -m "Prebid 4.x.x Release" git push ``` -3. Verify Release +### 4. Verify the Release Make sure your there are no more merges to master branch. Prebid code is clean and up to date. -4. Create a GitHub release +### 5. Create a GitHub release + + Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct version is set and the master branch is selected in the dropdown. Click `Publish release`. GitHub will create release tag. - Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct tag is in the dropdown. Click `Publish`. GitHub will create release tag. - - Pull these changes locally by running command + Pull these changes locally by running command ``` git pull git fetch --tags - ``` - + ``` + and verify the tag. -5. Update coveralls _(skip for legacy)_ +### 6. Update coveralls _(skip for legacy)_ We use https://coveralls.io/ to show parts of code covered by unit tests. @@ -80,35 +94,23 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for Run `gulp coveralls` to update code coverage history. -6. Distribute the code +### 7. Distribute the code - _Note: do not go to step 7 until step 6 has been verified completed._ + _Note: do not go to step 8 until step 7 has been verified completed._ Reach out to any of the Appnexus folks to trigger the jenkins job. - // TODO + // TODO: Jenkins job is moving files to appnexus cdn, pushing prebid.js to npm, purging cache and sending notification to slack. Move all the files from Appnexus CDN to jsDelivr and create bash script to do above tasks. -7. Post Release Version - - Update the version - Manually edit Prebid's package.json to become "1.x.x-pre" (using the values for the next release). Then commit your changes. +### 8. Increment Version for Next Release + + Update the version by manually editing Prebid's `package.json` to become "4.x.x-pre" (using the values for the next release). Then commit your changes. ``` git commit -m "Increment pre version" git push ``` - -8. Create new release draft - - Go to [github releases](https://github.com/prebid/Prebid.js/releases) and add a new draft for the next version of Prebid.js with the following template: -``` -## 🚀New Features - -## 🛠Maintenance - -## 🐛Bug Fixes -``` ## Beta Releases @@ -129,11 +131,8 @@ Characteristics of a `GA` release: ## FAQs **1. Is there flexibility in the schedule?** - -If a major bug is found in the current release, a maintenance patch will be done as soon as possible. - -It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. +* If a major bug is found in the current release, a maintenance patch will be done as soon as possible. +* It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. **2. What Pull Requests make it into a release?** - -Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). +* Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). diff --git a/allowedModules.js b/allowedModules.js index 2a521f781f9..2d23b35c501 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -12,7 +12,6 @@ module.exports = { 'modules': [ ...sharedWhiteList, 'criteo-direct-rsa-validate', - 'jsencrypt', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], @@ -21,7 +20,6 @@ module.exports = { 'fun-hooks/no-eval', 'just-clone', 'dlv', - 'dset', - 'deep-equal' + 'dset' ] }; diff --git a/browsers.json b/browsers.json index 91e0548d78a..4f2ea456f68 100644 --- a/browsers.json +++ b/browsers.json @@ -7,11 +7,11 @@ "device": null, "os": "Windows" }, - "bs_edge_18_windows_10": { + "bs_edge_90_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "18.0", + "browser_version": "90.0", "device": null, "os": "Windows" }, @@ -23,11 +23,11 @@ "device": null, "os": "Windows" }, - "bs_chrome_80_windows_10": { + "bs_chrome_90_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "80.0", + "browser_version": "90.0", "device": null, "os": "Windows" }, @@ -39,11 +39,11 @@ "device": null, "os": "Windows" }, - "bs_firefox_73_windows_10": { + "bs_firefox_88_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "73.0", + "browser_version": "88.0", "device": null, "os": "Windows" }, @@ -55,11 +55,11 @@ "device": null, "os": "Windows" }, - "bs_safari_11_mac_catalina": { + "bs_safari_14_mac_bigsur": { "base": "BrowserStack", - "os_version": "Catalina", + "os_version": "Big Sur", "browser": "safari", - "browser_version": "13.0", + "browser_version": "14.0", "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 64152baa7ba..8609177a8b9 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'); @@ -31,7 +31,7 @@ const execa = require('execa'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '*/\n'; var port = 9999; const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; @@ -71,7 +71,13 @@ 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', + './*.js' + ], { base: './' }) .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) @@ -86,7 +92,8 @@ function viewCoverage(done) { connect.server({ port: coveragePort, root: 'build/coverage/lcov-report', - livereload: false + livereload: false, + debug: true }); opens('http://' + mylocalhost + ':' + coveragePort); done(); @@ -94,6 +101,19 @@ function viewCoverage(done) { viewCoverage.displayName = 'view-coverage'; +// View the reviewer tools page +function viewReview(done) { + var mylocalhost = (argv.host) ? argv.host : 'localhost'; + var reviewUrl = 'http://' + mylocalhost + ':' + port + '/integrationExamples/reviewerTools/index.html'; // reuse the main port from 9999 + + // console.log(`stdout: opening` + reviewUrl); + + opens(reviewUrl); + done(); +}; + +viewReview.displayName = 'view-review'; + // Watch Task with Live Reload function watch(done) { var mainWatcher = gulp.watch([ @@ -110,6 +130,7 @@ function watch(done) { connect.server({ https: argv.https, port: port, + host: FAKE_SERVER_HOST, root: './', livereload: true }); @@ -119,6 +140,12 @@ function watch(done) { done(); }; +function makeModuleList(modules) { + return modules.map(module => { + return '"' + module + '"' + }); +} + function makeDevpackPkg() { var cloned = _.cloneDeep(webpackConfig); cloned.devtool = 'source-map'; @@ -130,6 +157,7 @@ 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()); } @@ -146,11 +174,16 @@ function makeWebpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(uglify()) + .pipe(terser()) + .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) .pipe(gulp.dest('build/dist')); } +function getModulesListToAddInBanner(modules) { + return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; +} + function gulpBundle(dev) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } @@ -200,6 +233,8 @@ function bundle(dev, moduleArr) { return gulp.src( entries ) + // Need to uodate the "Modules: ..." section in comment with the current modules list + .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { @@ -264,7 +299,7 @@ function test(done) { } else { var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + var browserOverride = helpers.parseBrowserArgs(argv); if (browserOverride.length > 0) { karmaConf.browsers = browserOverride; } @@ -382,4 +417,8 @@ gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-p gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step +// build task for reviewers, runs test-coverage, serves, without watching +gulp.task(viewReview); +gulp.task('review-start', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, testCoverage), viewReview)); + module.exports = nodeBundle; diff --git a/integrationExamples/gpt/adUnitFloors.html b/integrationExamples/gpt/adUnitFloors.html new file mode 100644 index 00000000000..bb48a20ef78 --- /dev/null +++ b/integrationExamples/gpt/adUnitFloors.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html new file mode 100644 index 00000000000..e8920cf2ee1 --- /dev/null +++ b/integrationExamples/gpt/adloox.html @@ -0,0 +1,232 @@ + + + + Prebid Display/Video Merged Auction with Adloox Integration + + + + + + + + +

Prebid Display/Video Merged Auction with Adloox Integration

+ +

div-1

+
+ +
+ +

div-2

+
+ +
+ +

video-1

+
+ + + + 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_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/audigentSegments_example.html b/integrationExamples/gpt/audigentSegments_example.html deleted file mode 100644 index 7739b558327..00000000000 --- a/integrationExamples/gpt/audigentSegments_example.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - - - - - - - - -

Audigent Segments Prebid

- -
- -
-TDID: -
-
- -Audigent Segments: -
-
- - diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index aef8b7f1654..04d4736c631 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,9 +1,4 @@ - - - - + - - - - - - - - - - - - -

DigiTrust Prebid Full Sample

- - -

- This sample shows the simplest integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

- -
- -
- -
- - - - diff --git a/integrationExamples/gpt/digitrust_Simple.html b/integrationExamples/gpt/digitrust_Simple.html deleted file mode 100644 index 2581c6ce7cc..00000000000 --- a/integrationExamples/gpt/digitrust_Simple.html +++ /dev/null @@ -1,230 +0,0 @@ - - - Simple DigiTrust Prebid - No Framework - - - - - - - - - - - - - - -

DigiTrust Prebid Sample - No Framework

- -

- This sample shows the simplest integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

-
- -
- -
- - diff --git a/integrationExamples/gpt/digitrust_cmp_test.html b/integrationExamples/gpt/digitrust_cmp_test.html deleted file mode 100644 index 6f0a70188f3..00000000000 --- a/integrationExamples/gpt/digitrust_cmp_test.html +++ /dev/null @@ -1,192 +0,0 @@ - - - CMP Simple DigiTrust Prebid - No Framework - - - - - - - - - - - - - -

DigiTrust Prebid Sample - No Framework

- -

- This sample tests cmp behavior with simple integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

-
- -
- -
- - - diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index de0630178f1..2d70af8d34f 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -82,7 +82,7 @@ - + + + - +

Prebid.js Test

Div-1
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/haloRtdProvider_example.html new file mode 100644 index 00000000000..14debbd2698 --- /dev/null +++ b/integrationExamples/gpt/haloRtdProvider_example.html @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + +

Halo RTD Prebid

+ +
+ +
+ +Halo Id: +
+
+ +Halo Real-Time Data: +
+
+ + diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html new file mode 100644 index 00000000000..07a4f0fe1c5 --- /dev/null +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -0,0 +1,342 @@ + + + + + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

+ + + + + + + + + + + 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/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html new file mode 100644 index 00000000000..41c27b70ece --- /dev/null +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -0,0 +1,106 @@ + + + + + + + JW Player RTD Provider Example + + + + + + + + +
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 new file mode 100644 index 00000000000..b6a22096c90 --- /dev/null +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -0,0 +1,267 @@ + + + + + + + + + + + +

+

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + + + diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html index 7761178efa8..37902edd979 100644 --- a/integrationExamples/gpt/prebidServer_example.html +++ b/integrationExamples/gpt/prebidServer_example.html @@ -56,10 +56,10 @@ s2sConfig : { accountId : '1', enabled : true, //default value set to false + defaultVendor: 'appnexus', bidders : ['appnexus'], timeout : 1000, //default value is 1000 adapter : 'prebidServer', //if we have any other s2s adapter, default value is s2s - endpoint : 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' } }); diff --git a/integrationExamples/gpt/reconciliationRtdProvider_example.html b/integrationExamples/gpt/reconciliationRtdProvider_example.html new file mode 100644 index 00000000000..4f9b663c22d --- /dev/null +++ b/integrationExamples/gpt/reconciliationRtdProvider_example.html @@ -0,0 +1,102 @@ + + + + + + + Reconciliation RTD Provider Example + + + + + + + + +
Div-1
+
+ +
+ + 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/sirdataRtdProvider_example.html b/integrationExamples/gpt/sirdataRtdProvider_example.html new file mode 100644 index 00000000000..444c9133905 --- /dev/null +++ b/integrationExamples/gpt/sirdataRtdProvider_example.html @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + +
+

+Give consent or make a choice in Europe. Module will add key/value pairs in ad calls. Check out for sd_rtd key in Google Ad call (https://securepubads.g.doubleclick.net/gampad/ads...) and in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : tags[0].keywords.key[sd_rtd] should have an array of string as value. This array will mix user segments and/or page categories based on user's choices. +

+
+

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 4acbe0aae31..dfea06d17d0 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -1,276 +1,324 @@ - - - + + User ID Modules Example - - + } + ] + } + ]; - + - pbjs.que.push(function() { - pbjs.setConfig({ - debug: true, - consentManagement: { - cmpApi: 'iab', - timeout: 1000, - allowAuctionWithoutConsent: true - }, - // consentManagement: { - // cmpApi: 'static', - // consentData: { - // consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' - // vendorData: { - // purposeConsents: { - // '1': true - // } - // } - // } - // }, - usersync: { - userIds: [{ - name: "unifiedId", - params: { - partner: "prebid", - url: "http://match.adsrvr.org/track/rid?ttd_pid=prebid&fmt=json" - }, - storage: { - type: "html5", - name: "unifiedid", - expires: 30 - }, - },{ - name: "intentIqId", - params: { - partner: 0, //Set your real IntentIQ partner ID here for production. - }, - storage: { - type: "cookie", - name: "intentIqId", - expires: 30 - }, - }, - { - name: "id5Id", - params: { - partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid - }, - storage: { - type: "cookie", - name: "id5id", - expires: 90, - refreshInSeconds: 8*3600 // Refresh frequency of cookies, defaulting to 'expires' - }, + + function sendAdserverRequest() { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + googletag.cmd.push(function () { + pbjs.que.push(function () { + pbjs.setTargetingForGPTAsync(); + googletag.pubads().refresh(); + }); + }); + } - + setTimeout(function () { + sendAdserverRequest(); + }, FAILSAFE_TIMEOUT); + - + + + -

Rubicon Project Prebid

+

User ID Modules Example

+ +

Generated EIDs

+ +

 
-
+

Ad Slot

+
-
+
+ diff --git a/integrationExamples/mass/index.html b/integrationExamples/mass/index.html new file mode 100644 index 00000000000..3b034957d13 --- /dev/null +++ b/integrationExamples/mass/index.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + +
+

Note: for this example to work, you need access to a bid simulation tool from your MASS enabled Exchange partner.

+
+ +
+
+ + 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 new file mode 100644 index 00000000000..9f4944884a0 --- /dev/null +++ b/integrationExamples/postbid/postbid_prebidServer_example.html @@ -0,0 +1,87 @@ + + + + + + + + + + + diff --git a/integrationExamples/reviewerTools/index.html b/integrationExamples/reviewerTools/index.html new file mode 100755 index 00000000000..2732cb4fd88 --- /dev/null +++ b/integrationExamples/reviewerTools/index.html @@ -0,0 +1,40 @@ + + + + + + + Prebid Reviewer Tools + + + + +
+
+
+

Reviewer Tools

+

Below are links to the most common tool used by Prebid reviewers. For more info on PR review processes check out the General PR Review Process page on Github.

+

Common

+ +

Other Tools

+ +

Documentation & Training Material

+ +
+
+
+ + \ No newline at end of file diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 712ef14caa1..cf5999ba85e 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) { @@ -170,6 +170,16 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { plugins: plugins } + + // To ensure that, we are able to run single spec file + // here we are adding preprocessors, when file is passed + if (file) { + config.files.forEach((file) => { + config.preprocessors[file] = ['webpack', 'sourcemap']; + }); + delete config.preprocessors['test/test_index.js']; + } + setReporters(config, codeCoverage, browserstack); setBrowsers(config, browserstack); return config; diff --git a/modules/.submodules.json b/modules/.submodules.json index ba8af4e6550..3a3b94ea469 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -2,22 +2,59 @@ "userId": [ "unifiedIdSystem", "pubCommonIdSystem", - "digiTrustIdSystem", "id5IdSystem", "parrableIdSystem", "britepoolIdSystem", "liveIntentIdSystem", "lotamePanoramaId", + "merkleIdSystem", "criteoIdSystem", "netIdSystem", "identityLinkIdSystem", - "sharedIdSystem" + "sharedIdSystem", + "intentIqIdSystem", + "zeotapIdPlusIdSystem", + "haloIdSystem", + "quantcastIdSystem", + "deepintentDpesIdSystem", + "nextrollIdSystem", + "idxIdSystem", + "fabrickIdSystem", + "verizonMediaIdSystem", + "pubProvidedIdSystem", + "mwOpenLinkIdSystem", + "tapadIdSystem", + "novatiqIdSystem", + "uid2IdSystem", + "admixerIdSystem", + "dmdIdSystem", + "akamaiDAPId", + "flocIdSystem", + "amxIdSystem", + "naveggId", + "imuIdSystem", + "publinkIdSystem" ], "adpod": [ "freeWheelAdserverVideo", "dfpAdServerVideo" ], "rtdModule": [ - "browsiRtdProvider" + "browsiRtdProvider", + "dgkeywordRtdProvider", + "geoedgeRtdProvider", + "haloRtdProvider", + "iasRtdProvider", + "jwplayerRtdProvider", + "medianetRtdProvider", + "optimeraRtdProvider", + "permutiveRtdProvider", + "reconciliationRtdProvider", + "sirdataRtdProvider", + "timeoutRtdProvider" + ], + "fpdModule": [ + "enrichmentFpdModule", + "validationFpdModule" ] } diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 5df2af7f32e..4b8028d97fd 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,12 +1,37 @@ 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'; 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'; -const MEDIA_TYPE = 'banner'; + const CURRENCY = 'USD'; +const GUID_PATTERN = /^[a-zA-Z0-9_-]{22}$/; + +const PRODUCT = { + SIAB: 'siab', + INVIEW: 'inview', + INSTREAM: 'instream' +}; + +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; const adapterState = { uniqueSiteIds: [] @@ -14,57 +39,122 @@ const adapterState = { const NON_MEASURABLE = 'nm'; -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { - 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, - netRevenue: true +// **************************** VALIDATION *************************** // +function isBidRequestValid(bid) { + return ( + _validateBasic(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); +} + +function _validateBasic(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (!_validateGUID(bid)) { + return false; } + + return true; } -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; +function _validateGUID(bid) { + const siteID = utils.deepAccess(bid, 'params.siteId', '') || ''; + if (siteID.trim().match(GUID_PATTERN) === null) { + return false; + } + + return true; } -function _getViewability(element, topWin, { w, h } = {}) { - return topWin.document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, { w, h }) - : 0; +function _validateBanner(bid) { + const banner = utils.deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules + if (banner === undefined) { + return true; + } + + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; } -function _mapAdUnitPathToElementId(adUnitCode) { - if (utils.isGptPubadsDefined()) { - // eslint-disable-next-line no-undef - const adSlots = googletag.pubads().getSlots(); - const isMatchingAdSlot = utils.isSlotMatchingAdUnitCode(adUnitCode); +function _validateVideo(bid) { + const videoAdUnit = utils.deepAccess(bid, 'mediaTypes.video'); + const videoBidderParams = utils.deepAccess(bid, 'params.video', {}); - for (let i = 0; i < adSlots.length; i++) { - if (isMatchingAdSlot(adSlots[i])) { - const id = adSlots[i].getSlotElementId(); + // If there's no video no need to validate against video rules + if (videoAdUnit === undefined) { + return true; + } - utils.logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } - return id; - } - } + if (!videoAdUnit.context) { + return false; } - utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + const videoParams = { + ...videoAdUnit, + ...videoBidderParams + }; - return null; + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + return false; + } + + // If placement if defined, it must be a number + if ( + typeof videoParams.placement !== 'undefined' && + typeof videoParams.placement !== 'number' + ) { + return false; + } + + // If startdelay is defined it must be a number + if ( + videoAdUnit.context === 'instream' && + typeof videoParams.startdelay !== 'undefined' && + typeof videoParams.startdelay !== 'number' + ) { + return false; + } + + return true; } -function _getAdSlotHTMLElement(adUnitCode) { - return document.getElementById(adUnitCode) || - document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +// **************************** BUILD REQUESTS *************************** // +// 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 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); + + return bidRequests.map(bidRequest => _createServerRequest( + { + bidRequest, + gdprConsent, + uspConsent, + pageUrl + }) + ); } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request @@ -72,46 +162,28 @@ function _getAdSlotHTMLElement(adUnitCode) { function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { const ttxRequest = {}; const params = bidRequest.params; - const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); - const sizes = _transformSizes(bidRequest.sizes); - let format; - - // We support size based bidfloors so obtain one if there's a rule associated - if (typeof bidRequest.getFloor === 'function') { - let getFloor = bidRequest.getFloor.bind(bidRequest); - - format = sizes.map((size) => { - const formatExt = _getBidFloors(getFloor, size); + /* + * Infer data for the request payload + */ + ttxRequest.imp = [{}]; - return Object.assign({}, size, formatExt); - }); - } else { - format = sizes; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { + ttxRequest.imp[0].banner = { + ..._buildBannerORTB(bidRequest) + } } - const minSize = _getMinSize(sizes); - - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) - : NON_MEASURABLE; - - const contributeViewability = ViewabilityContributor(viewabilityAmount); + if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); + } - /* - * Infer data for the request payload - */ - ttxRequest.imp = []; - ttxRequest.imp[0] = { - banner: { - format - }, - ext: { - ttx: { - prod: params.productId - } + ttxRequest.imp[0].ext = { + ttx: { + prod: _getProduct(bidRequest) } }; + ttxRequest.site = { id: params.siteId }; if (pageUrl) { @@ -122,18 +194,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl // therefore in ad targetting process ttxRequest.id = bidRequest.bidId; - // Set GDPR related fields - ttxRequest.user = { - ext: { - consent: gdprConsent.consentString - } - }; - ttxRequest.regs = { - ext: { - gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0, - us_privacy: uspConsent || null - } - }; + if (gdprConsent.consentString) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'consent', + gdprConsent.consentString + ) + } + + if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { + ttxRequest.user = setExtension( + ttxRequest.user, + 'eids', + bidRequest.userIdAsEids + ) + } + + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'gdpr', + Number(gdprConsent.gdprApplies) + ); + + if (uspConsent) { + ttxRequest.regs = setExtension( + ttxRequest.regs, + 'us_privacy', + uspConsent + ) + } + ttxRequest.ext = { ttx: { prebidStartedAt: Date.now(), @@ -145,11 +235,11 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; if (bidRequest.schain) { - ttxRequest.source = { - ext: { - schain: bidRequest.schain - } - } + ttxRequest.source = setExtension( + ttxRequest.source, + 'schain', + bidRequest.schain + ) } // Finally, set the openRTB 'test' param if this is to be a test bid @@ -167,59 +257,202 @@ 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; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; // Return the server request return { 'method': 'POST', 'url': url, - 'data': JSON.stringify(contributeViewability(ttxRequest)), + 'data': JSON.stringify(ttxRequest), 'options': options } } -// Sync object will always be of type iframe for TTX -function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConsent }) { - const ttxSettings = config.getConfig('ttxSettings'); - const syncUrl = (ttxSettings && ttxSettings.syncUrl) || SYNC_ENDPOINT; +// BUILD REQUESTS: SET EXTENSIONS +function setExtension(obj = {}, key, value) { + return Object.assign({}, obj, { + ext: Object.assign({}, obj.ext, { + [key]: value + }) + }); +} - const { consentString, gdprApplies } = gdprConsent; +// BUILD REQUESTS: SIZE INFERENCE +function _transformSizes(sizes) { + if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + return [ _getSize(sizes) ]; + } - const sync = { - type: 'iframe', - url: `${syncUrl}&id=${siteId}&gdpr_consent=${encodeURIComponent(consentString)}&us_privacy=${encodeURIComponent(uspConsent)}` - }; + return sizes.map(_getSize); +} - if (typeof gdprApplies === 'boolean') { - sync.url += `&gdpr=${Number(gdprApplies)}`; +function _getSize(size) { + return { + w: parseInt(size[0], 10), + h: parseInt(size[1], 10) } +} - return sync; +// BUILD REQUESTS: PRODUCT INFERENCE +function _getProduct(bidRequest) { + const { params, mediaTypes } = bidRequest; + + const { banner, video } = mediaTypes; + + if ((video && !banner) && video.context === 'instream') { + return PRODUCT.INSTREAM; + } + + return (params.productId === PRODUCT.INVIEW) ? (params.productId) : PRODUCT.SIAB; } -function _getBidFloors(getFloor, size) { - const bidFloors = getFloor({ +// BUILD REQUESTS: BANNER +function _buildBannerORTB(bidRequest) { + const bannerAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.banner', {}); + const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); + + const sizes = _transformSizes(bannerAdUnit.sizes); + + let format; + + // We support size based bidfloors so obtain one if there's a rule associated + if (typeof bidRequest.getFloor === 'function') { + format = sizes.map((size) => { + const bidfloors = _getBidFloors(bidRequest, size, BANNER); + + let formatExt; + if (bidfloors) { + formatExt = { + ext: { + ttx: { + bidfloors: [ bidfloors ] + } + } + } + } + + return Object.assign({}, size, formatExt); + }); + } else { + format = sizes; + } + + const minSize = _getMinSize(sizes); + + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : NON_MEASURABLE; + + const ext = contributeViewability(viewabilityAmount); + + return { + format, + ext + } +} + +// 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 videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + const video = {} + + const {w, h} = _getSize(videoParams.playerSize[0]); + video.w = w; + video.h = h; + + // Obtain all ORTB params related video from Ad Unit + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + const product = _getProduct(bidRequest); + + // Placement Inference Rules: + // - If no placement is defined then default to 2 (In Banner) + // - If product is instream (for instream context) then override placement to 1 + video.placement = video.placement || 2; + + 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); + + if (bidfloors) { + Object.assign(video, { + ext: { + ttx: { + bidfloors: [ bidfloors ] + } + } + }); + } + } + return video; +} + +// BUILD REQUESTS: BIDFLOORS +function _getBidFloors(bidRequest, size, mediaType) { + const bidFloors = bidRequest.getFloor({ currency: CURRENCY, - mediaType: MEDIA_TYPE, + mediaType, size: [ size.w, size.h ] }); if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { - return { - ext: { - ttx: { - bidfloors: [ bidFloors.floor ] - } + return bidFloors.floor; + } +} + +// BUILD REQUESTS: VIEWABILITY +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _mapAdUnitPathToElementId(adUnitCode) { + if (utils.isGptPubadsDefined()) { + // eslint-disable-next-line no-undef + const adSlots = googletag.pubads().getSlots(); + const isMatchingAdSlot = utils.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}`); + + return id; } } } + + utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + + return null; } -function _getSize(size) { - return { - w: parseInt(size[0], 10), - h: parseInt(size[1], 10) - } +function _getAdSlotHTMLElement(adUnitCode) { + return document.getElementById(adUnitCode) || + document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); } function _getMinSize(sizes) { @@ -239,14 +472,6 @@ function _getBoundingBox(element, { w, h } = {}) { return { width, height, left, top, right, bottom }; } -function _transformSizes(sizes) { - if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { - return [ _getSize(sizes) ]; - } - - return sizes.map(_getSize); -} - function _getIntersectionOfRects(rects) { const bbox = { left: rects[0].left, @@ -307,20 +532,16 @@ function _getPercentInView(element, topWin, { w, h } = {}) { /** * Viewability contribution to request.. */ -function ViewabilityContributor(viewabilityAmount) { - function contributeViewability(ttxRequest) { - const req = Object.assign({}, ttxRequest); - const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem)); - const banner = imp[0].banner = Object.assign({}, imp[0].banner); - const ext = banner.ext = Object.assign({}, banner.ext); - const ttx = ext.ttx = Object.assign({}, ext.ttx); +function contributeViewability(viewabilityAmount) { + const amount = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); - ttx.viewability = { amount: isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount) }; - - return req; - } - - return contributeViewability; + return { + ttx: { + viewability: { + amount + } + } + }; } function _isIframe() { @@ -331,53 +552,58 @@ function _isIframe() { } } -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { - return false; - } +// **************************** 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 (typeof bid.params.siteId === 'undefined' || typeof bid.params.productId === 'undefined') { - return false; + // 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 true; + return bidResponses; } -// NOTE: With regards to gdrp consent data, -// - the server independently infers gdpr applicability therefore, setting the default value to false -function buildRequests(bidRequests, bidderRequest) { - 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); +// All this assumes that only one bid is ever returned by ttx +function _createBidResponse(response) { + const isADomainPresent = + response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length; + const bid = { + 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, + mediaType: utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), + currency: response.cur, + netRevenue: true + } - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); -} + if (isADomainPresent) { + bid.meta = { + advertiserDomains: response.seatbid[0].bid[0].adomain + }; + } -// 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 (bid.mediaType === VIDEO) { + const vastType = utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); - // 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)); + if (vastType === 'xml') { + bid.vastXml = bid.ad; + } else { + bid.vastUrl = bid.ad; + } } - return bidResponses; + return bid; } +// **************************** USER SYNC *************************** // // Register one sync per unique guid so long as iframe is enable // Else no syncs // For logic on how we handle gdpr data see _createSyncs and module's unit tests @@ -395,11 +621,30 @@ function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { return syncUrls; } +// Sync object will always be of type iframe for TTX +function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConsent }) { + const ttxSettings = config.getConfig('ttxSettings'); + const syncUrl = (ttxSettings && ttxSettings.syncUrl) || SYNC_ENDPOINT; + + const { consentString, gdprApplies } = gdprConsent; + + const sync = { + type: 'iframe', + url: `${syncUrl}&id=${siteId}&gdpr_consent=${encodeURIComponent(consentString)}&us_privacy=${encodeURIComponent(uspConsent)}` + }; + + if (typeof gdprApplies === 'boolean') { + sync.url += `&gdpr=${Number(gdprApplies)}`; + } + + return sync; +} + export const spec = { NON_MEASURABLE, code: BIDDER_CODE, - + supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/33acrossBidAdapter.md b/modules/33acrossBidAdapter.md index c313f3b6e0b..c01c04251e5 100644 --- a/modules/33acrossBidAdapter.md +++ b/modules/33acrossBidAdapter.md @@ -10,23 +10,145 @@ Maintainer: headerbidding@33across.com Connects to 33Across's exchange for bids. -33Across bid adapter supports only Banner at present and follows MRA +33Across bid adapter supports Banner and Video at present and follows MRA # Sample Ad Unit: For Publishers +## Sample Banner only Ad Unit ``` var adUnits = [ { - code: '33across-hb-ad-123456-1', // ad slot HTML element ID - sizes: [ - [300, 250], - [728, 90] - ], - bids: [{ - bidder: '33across', - params: { - siteId: 'cxBE0qjUir6iopaKkGJozW', - productId: 'siab' + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + } + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Video only Ad Unit: Outstream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'outstream', + placement: 2 + ... // Aditional ORTB video params + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.playerHeight, + player_width: bid.playerWidth + } + } } - }] + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, // target div id to render video. + adResponse: adResponse + }); + }); + } + }, + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Multi-Format Ad Unit: Outstream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + }, + video: { + playerSize: [300, 250], + context: 'outstream', + placement: 2 + ... // Aditional ORTB video params + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.playerHeight, + player_width: bid.playerWidth + } + } + } + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, // target div id to render video. + adResponse: adResponse + }); + }); + } + }, + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Video only Ad Unit: Instream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'intstream', + placement: 1 + ... // Aditional ORTB video params + } + } + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'instream' + } + }] } ``` 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 new file mode 100644 index 00000000000..01c59616dc0 --- /dev/null +++ b/modules/a4gBidAdapter.js @@ -0,0 +1,92 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; + +const A4G_BIDDER_CODE = 'a4g'; +const A4G_CURRENCY = 'USD'; +const A4G_DEFAULT_BID_URL = 'https://ads.ad4game.com/v1/bid'; +const A4G_TTL = 120; + +const LOCATION_PARAM_NAME = 'siteurl'; +const ID_PARAM_NAME = 'id'; +const IFRAME_PARAM_NAME = 'if'; +const ZONE_ID_PARAM_NAME = 'zoneId'; +const SIZE_PARAM_NAME = 'size'; + +const ARRAY_PARAM_SEPARATOR = ';'; +const ARRAY_SIZE_SEPARATOR = ','; +const SIZE_SEPARATOR = 'x'; + +export const spec = { + code: A4G_BIDDER_CODE, + isBidRequestValid: function(bid) { + return bid.params && !!bid.params.zoneId; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let deliveryUrl = ''; + const idParams = []; + const sizeParams = []; + const zoneIds = []; + + utils._each(validBidRequests, function(bid) { + if (!deliveryUrl && typeof bid.params.deliveryUrl === 'string') { + deliveryUrl = bid.params.deliveryUrl; + } + idParams.push(bid.bidId); + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + sizeParams.push(bidSizes.map(size => size.join(SIZE_SEPARATOR)).join(ARRAY_SIZE_SEPARATOR)); + zoneIds.push(bid.params.zoneId); + }); + + if (!deliveryUrl) { + deliveryUrl = A4G_DEFAULT_BID_URL; + } + + let data = { + [IFRAME_PARAM_NAME]: 0, + [LOCATION_PARAM_NAME]: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href, + [SIZE_PARAM_NAME]: sizeParams.join(ARRAY_PARAM_SEPARATOR), + [ID_PARAM_NAME]: idParams.join(ARRAY_PARAM_SEPARATOR), + [ZONE_ID_PARAM_NAME]: zoneIds.join(ARRAY_PARAM_SEPARATOR) + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + + return { + method: 'GET', + url: deliveryUrl, + data: data + }; + }, + + interpretResponse: function(serverResponses, request) { + const bidResponses = []; + utils._each(serverResponses.body, function(response) { + if (response.cpm > 0) { + const bidResponse = { + requestId: response.id, + creativeId: response.crid || response.id, + cpm: response.cpm, + width: response.width, + height: response.height, + currency: A4G_CURRENCY, + netRevenue: true, + ttl: A4G_TTL, + ad: response.ad, + meta: { + advertiserDomains: response.adomain && response.adomain.length > 0 ? response.adomain : [] + } + }; + bidResponses.push(bidResponse); + } + }); + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/a4gBidAdapter.md b/modules/a4gBidAdapter.md index dcab312ed29..70f110724b0 100644 --- a/modules/a4gBidAdapter.md +++ b/modules/a4gBidAdapter.md @@ -6,32 +6,40 @@ Maintainer: devops@ad4game.com # Description -Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com). +Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com). # Test Parameters ``` var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, bids: [ { bidder: 'a4g', params: { zoneId: 59304, - deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + deliveryUrl: 'https://dev01.ad4game.com/v1/bid' } } ] },{ code: 'test-div', - sizes: [[300, 50]], // a mobile size + mediaTypes: { + banner: { + sizes: [[300, 50]], // a mobile size + } + }, bids: [ { bidder: 'a4g', params: { zoneId: 59354, - deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + deliveryUrl: 'https://dev01.ad4game.com/v1/bid' } } ] 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 9bd22ef1f0d..2400952367f 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,13 +1,14 @@ import * as utils from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ajax} from '../src/ajax.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -31,20 +32,25 @@ export const spec = { return []; } return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; - const size = sizes.split('x'); + let sizes = [] + if (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER] && bidRequest.mediaTypes[BANNER].sizes) { + sizes = bidRequest.mediaTypes[BANNER].sizes; + } else if (bidRequest.mediaTypes[VIDEO] && bidRequest.mediaTypes[VIDEO].playerSize) { + sizes = bidRequest.mediaTypes[VIDEO].playerSize + } const jaySupported = 'atob' in window && 'currentScript' in document; const device = getDevice(); const payload = { placementId: bidRequest.params.placementId, - width: size[0], - height: size[1], + sizes: sizes, bidId: bidRequest.bidId, categories: bidRequest.params.categories, referer: bidderRequest.refererInfo.referer, jaySupported: jaySupported, device: device, - adapterVersion: 2 + adapterVersion: 5, + mediaTypes: bidRequest.mediaTypes, + gdprConsent: bidderRequest.gdprConsent }; return { method: 'POST', @@ -72,9 +78,8 @@ export const spec = { return bidResponses; }, onBidWon: function (bid) { - if (!bid['nurl']) { return false; } - ajax(bid['nurl'], null); - return true; + if (!bid['nurl']) { return; } + utils.triggerPixel(bid['nurl']); } }; diff --git a/modules/ablidaBidAdapter.md b/modules/ablidaBidAdapter.md index 70e6576cd30..e0a9f3f9405 100644 --- a/modules/ablidaBidAdapter.md +++ b/modules/ablidaBidAdapter.md @@ -27,6 +27,46 @@ Module that connects to Ablida's bidder for bids. } } ] + }, { + code: 'native-ad-div', + mediaTypes: { + native: { + image: { + sendId: true, + required: true + }, + title: { + required: true + }, + body: { + required: true + } + } + }, + bids: [ + { + bidder: 'ablida', + params: { + placementId: 'native-demo' + } + } + ] + }, { + code: 'video-ad', + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream' + } + }, + bids: [ + { + bidder: 'ablida', + params: { + placementId: 'instream-demo' + } + } + ] } - ]; + ]; ``` diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js new file mode 100644 index 00000000000..a3d78a69d91 --- /dev/null +++ b/modules/adWMGBidAdapter.js @@ -0,0 +1,319 @@ +'use strict'; + +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'adWMG'; +const ENDPOINT = 'https://hb.adwmg.com/hb'; +let SYNC_ENDPOINT = 'https://hb.adwmg.com/cphb.html?'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['wmg'], + supportedMediaTypes: [BANNER], + isBidRequestValid: (bid) => { + if (bid.bidder !== BIDDER_CODE) { + return false; + } + + if (!(bid.params.publisherId)) { + return false; + } + + return true; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const timeout = bidderRequest.timeout || 0; + const debug = config.getConfig('debug') || false; + const referrer = bidderRequest.refererInfo.referer; + const locale = window.navigator.language && window.navigator.language.length > 0 ? window.navigator.language.substr(0, 2) : ''; + const domain = config.getConfig('publisherDomain') || (window.location && window.location.host ? window.location.host : ''); + const ua = window.navigator.userAgent.toLowerCase(); + const additional = spec.parseUserAgent(ua); + + return validBidRequests.map(bidRequest => { + const checkFloorValue = (value) => { + if (isNaN(parseFloat(value))) { + return 0; + } else return parseFloat(value); + } + + const adUnit = { + code: bidRequest.adUnitCode, + bids: { + bidder: bidRequest.bidder, + params: { + publisherId: bidRequest.params.publisherId, + IABCategories: bidRequest.params.IABCategories || [], + floorCPM: bidRequest.params.floorCPM ? checkFloorValue(bidRequest.params.floorCPM) : 0 + } + }, + 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, + locale: locale, + domain: domain, + os: additional.os, + osv: additional.osv, + devicetype: additional.devicetype + }; + + if (bidderRequest.gdprConsent) { + request.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consentString: bidderRequest.gdprConsent.consentString + }; + } + + /* if (bidderRequest.uspConsent) { + request.uspConsent = bidderRequest.uspConsent; + } + */ + if (bidRequest.userId && bidRequest.userId.pubcid) { + request.userId = { + pubcid: bidRequest.userId.pubcid + }; + } + + if (debug) { + request.debug = debug; + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request) + } + }); + }, + interpretResponse: (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, + meta: { + advertiserDomains: response.adomain && response.adomain.length ? response.adomain : [], + mediaType: 'banner' + } + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + if (gdprConsent && SYNC_ENDPOINT.indexOf('gdpr') === -1) { + SYNC_ENDPOINT = utils.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); + } + + if (SYNC_ENDPOINT.slice(-1) === '&') { + SYNC_ENDPOINT = SYNC_ENDPOINT.slice(0, -1); + } + + /* if (uspConsent) { + SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); + } */ + let syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: SYNC_ENDPOINT + }); + } + return syncs; + }, + parseUserAgent: (ua) => { + function detectDevice() { + 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 2; + } + + function detectOs() { + const module = { + options: [], + header: [navigator.platform, ua, navigator.appVersion, navigator.vendor, window.opera], + dataos: [{ + name: 'Windows Phone', + value: 'Windows Phone', + version: 'OS' + }, + { + name: 'Windows', + value: 'Win', + version: 'NT' + }, + { + name: 'iOS', + value: 'iPhone', + version: 'OS' + }, + { + name: 'iOS', + 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' + } + ], + init: function () { + var agent = this.header.join(' '); + var os = this.matchItem(agent, this.dataos); + return { + os + }; + }, + + getVersion: function (name, version) { + if (name === 'Windows') { + switch (parseFloat(version).toFixed(1)) { + case '5.0': + return '2000'; + case '5.1': + return 'XP'; + case '5.2': + return 'Server 2003'; + case '6.0': + return 'Vista'; + case '6.1': + return '7'; + case '6.2': + return '8'; + case '6.3': + return '8.1'; + default: + return version || 'other'; + } + } else return version || 'other'; + }, + + matchItem: function (string, data) { + var i = 0; + var j = 0; + var regex, regexv, match, matches, 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 = 'other'; + } + return { + name: data[i].name, + version: this.getVersion(data[i].name, version) + }; + } + } + return { + name: 'unknown', + version: 'other' + }; + } + }; + + var e = module.init(); + + return { + os: e.os.name || '', + osv: e.os.version || '' + } + } + + return { + devicetype: detectDevice(), + os: detectOs().os, + osv: detectOs().osv + } + } +}; +registerBidder(spec); diff --git a/modules/adWMGBidAdapter.md b/modules/adWMGBidAdapter.md new file mode 100644 index 00000000000..ec5541e6168 --- /dev/null +++ b/modules/adWMGBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: adWMG Adapter +Module Type: Bidder Adapter +Maintainer: wbid@adwmg.com +``` + +# Description + +Module that connects to adWMG demand sources to fetch bids. Supports 'banner' ad format. + +# Bid Parameters + +| Key | Required | Example | Description | +| --------------- | -------- | -----------------------------| ------------------------------- | +| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | +| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | +| `floorCPM` | no | `0.5` | Floor price for adUnit | + + +# Test Parameters + +```javascript +var adUnits = [{ + code: 'wmg-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'adWMG', + params: { + publisherId: '5cebea3c9eea646c7b623d5e', + IABCategories: ['IAB1', 'IAB5'] + }, + }] +}] \ No newline at end of file diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b2ef5dafb41..ffa955290be 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,64 +1,175 @@ import find from 'core-js-pure/features/array/find.js'; import * as utils 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 { loadExternalScript } from '../src/adloader.js'; +import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; import { getStorageManager } from '../src/storageManager.js'; - +import { getRefererInfo } from '../src/refererDetection.js'; +import { createEidsArray } from './userId/eids.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; const BIDDER_CODE = 'adagio'; -const VERSION = '2.2.2'; +const LOG_PREFIX = 'Adagio:'; const FEATURES_VERSION = '1'; -const ENDPOINT = 'https://mp.4dex.io/prebid'; -const SUPPORTED_MEDIA_TYPES = ['banner']; +export const ENDPOINT = 'https://mp.4dex.io/prebid'; +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; -const storage = getStorageManager(GVLID, 'adagio'); +export const storage = getStorageManager(GVLID, 'adagio'); +export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; +const MAX_SESS_DURATION = 30 * 60 * 1000; +const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; +const ADAGIO_PUBKEY_E = 65537; +const CURRENCY = 'USD'; +const DEFAULT_FLOOR = 0.1; + +// This provide a whitelist and a basic validation +// of OpenRTB 2.5 options used by the Adagio SSP. +// 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), + '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), + '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), + '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), + '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, + '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 => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1) +}; + +let currentWindow; + +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 = 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(exchangeData, adagioStorage, data); -export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 -jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl -t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj -pV6EP3MTLosuUEpLaQIDAQAB ------END PUBLIC KEY-----`; + internal.enqueue({ + action: 'session', + ts: Date.now(), + data: exchangeData + }); + }, + + getExchangeData() { + return exchangeData + } + }; +})(); export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { - utils.logWarn('Adagio Script not found'); + utils.logWarn(`${LOG_PREFIX} script not found.`); return; } const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; if (!hashRgx.test(ls)) { - utils.logWarn('No hash found in Adagio script'); + utils.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('Start Adagio script'); + if (verify(content, hash, ADAGIO_PUBKEY, ADAGIO_PUBKEY_E)) { + utils.logInfo(`${LOG_PREFIX} start script.`); Function(ls)(); // eslint-disable-line no-new-func } else { - utils.logWarn('Invalid Adagio script found'); + utils.logWarn(`${LOG_PREFIX} invalid script found.`); storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); } } } catch (err) { - // + utils.logError(LOG_PREFIX, err); } } export function getAdagioScript() { storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { - adagioScriptFromLocalStorageCb(ls) + internal.adagioScriptFromLocalStorageCb(ls); + }); + + storage.localStorageIsEnabled(isValid => { + if (isValid) { + loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE); + } else { + // 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) { + utils.logInfo(`${LOG_PREFIX} unable to clear Adagio scripts from localstorage.`); + } + } }); } @@ -72,403 +183,774 @@ function canAccessTopWindow() { } } +function getCurrentWindow() { + return currentWindow || utils.getWindowSelf(); +} + +function isSafeFrameWindow() { + const ws = utils.getWindowSelf(); + return !!(ws.$sf && ws.$sf.ext); +} + function initAdagio() { - const w = utils.getWindowTop(); + if (canAccessTopWindow()) { + currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf(); + } + + const w = internal.getCurrentWindow(); w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + 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(); - getAdagioScript(); + storage.getDataFromLocalStorage('adagio', (storageData) => { + try { + GlobalExchange.prepareExchangeData(storageData); + } catch (e) { + utils.logError(LOG_PREFIX, e); + } + }); - loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE) + getAdagioScript(); } -if (canAccessTopWindow()) { - initAdagio(); -} +function enqueue(ob) { + const w = internal.getCurrentWindow(); -export const _features = { - getPrintNumber: function (adUnitCode) { - const adagioAdUnit = _getOrAddAdagioAdUnit(adUnitCode); - return adagioAdUnit.printNumber || 1; - }, + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.queue.push(ob); +}; - getPageDimensions: function () { - const viewportDims = _features.getViewPortDimensions().split('x'); - const w = utils.getWindowTop(); - const body = w.document.body; - if (!body) { - return '' - } - const html = w.document.documentElement; - const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); +function getPageviewId() { + const w = internal.getCurrentWindow(); - return viewportDims[0] + 'x' + pageHeight; - }, + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); + + return w.ADAGIO.pageviewId; +}; - getViewPortDimensions: function () { - let viewPortWidth; - let viewPortHeight; - const w = utils.getWindowTop(); - const d = w.document; +function getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; + return { + userAgent: navigator.userAgent, + language: navigator[language], + dnt: utils.getDNT() ? 1 : 0, + geo: {}, + js: 1 + }; +}; - if (w.innerWidth) { - viewPortWidth = w.innerWidth; - viewPortHeight = w.innerHeight; +function getSite(bidderRequest) { + let domain = ''; + let page = ''; + let referrer = ''; + + const { refererInfo } = bidderRequest; + + if (canAccessTopWindow()) { + const wt = utils.getWindowTop(); + domain = wt.location.hostname; + page = wt.location.href; + referrer = wt.document.referrer || ''; + } else if (refererInfo.reachedTop) { + const url = utils.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]); + domain = url.hostname; + } + + return { + domain, + page, + referrer + }; +}; + +function getElementFromTopWindow(element, currentWindow) { + try { + if (utils.getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + } + return element; } else { - viewPortWidth = d.getElementsByTagName('body')[0].clientWidth; - viewPortHeight = d.getElementsByTagName('body')[0].clientHeight; + const frame = currentWindow.frameElement; + const frameClientRect = frame.getBoundingClientRect(); + const elementClientRect = element.getBoundingClientRect(); + + if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) { + return false; + } + + return getElementFromTopWindow(frame, currentWindow.parent); } + } catch (err) { + utils.logWarn(`${LOG_PREFIX}`, err); + return false; + } +}; - return viewPortWidth + 'x' + viewPortHeight; - }, +function autoDetectAdUnitElementIdFromGpt(adUnitCode) { + const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode); + + if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { + return autoDetectedAdUnit.divId; + } +}; - isDomLoading: function () { - const w = utils.getWindowTop(); - let performance = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance; - let domLoading = -1; +function isRendererPreferredFromPublisher(bidRequest) { + // renderer defined at adUnit level + const adUnitRenderer = utils.deepAccess(bidRequest, 'renderer'); + const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) domLoading = val; - } - return domLoading; - }, + // renderer defined at adUnit.mediaTypes level + const mediaTypeRenderer = utils.deepAccess(bidRequest, 'mediaTypes.video.renderer'); + const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render); - getSlotPosition: function (element) { - if (!element) return ''; + return !!( + (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || + (hasValidMediaTypeRenderer && !(mediaTypeRenderer.backupOnly === true)) + ); +} - const w = utils.getWindowTop(); - const d = w.document; - const el = element; +/** + * + * @param {object} adagioStorage + * @returns {boolean} + */ +function isNewSession(adagioStorage) { + const now = Date.now(); + const { lastActivityTime, vwSmplg } = utils.deepAccess(adagioStorage, 'session', {}); + return ( + !utils.isNumber(lastActivityTime) || + !utils.isNumber(vwSmplg) || + (now - lastActivityTime) > MAX_SESS_DURATION + ) +} - let box = el.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 = w.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = w.pageXOffset || docEl.scrollLeft || body.scrollLeft; +function setPlayerName(bidRequest) { + const playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; - const elComputedStyle = w.getComputedStyle(el, null); - const elComputedDisplay = elComputedStyle.display || 'block'; - const mustDisplayElement = elComputedDisplay === 'none'; + if (playerName === 'other') { + utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); + } - if (mustDisplayElement) { - el.style = el.style || {}; - el.style.display = 'block'; - box = el.getBoundingClientRect(); - el.style.display = elComputedDisplay; - } + return playerName; +} - const position = { - x: Math.round(box.left + scrollLeft - clientLeft), - y: Math.round(box.top + scrollTop - clientTop) - }; +export const internal = { + enqueue, + getPageviewId, + getDevice, + getSite, + getElementFromTopWindow, + getRefererInfo, + adagioScriptFromLocalStorageCb, + getCurrentWindow, + canAccessTopWindow, + isRendererPreferredFromPublisher, + isNewSession +}; - return position.x + 'x' + position.y; - }, +function _getGdprConsent(bidderRequest) { + if (!utils.deepAccess(bidderRequest, 'gdprConsent')) { + return false; + } - getTimestamp: function () { - return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; - }, + const { + apiVersion, + gdprApplies, + consentString, + allowAuctionWithoutConsent + } = bidderRequest.gdprConsent; + + return utils.cleanObj({ + apiVersion, + consentString, + consentRequired: gdprApplies ? 1 : 0, + allowAuctionWithoutConsent: allowAuctionWithoutConsent ? 1 : 0 + }); +} - getDevice: function () { - if (!canAccessTopWindow()) return false; - const w = utils.getWindowTop(); - const ua = w.navigator.userAgent; +function _getCoppa() { + return { + required: config.getConfig('coppa') === true ? 1 : 0 + }; +} - 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 - }, +function _getUspConsent(bidderRequest) { + return (utils.deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; +} - getBrowser: function () { - const w = utils.getWindowTop(); - const ua = w.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 || w.MSStream ? 'ie' : 'unknow'; - }, +function _getSchain(bidRequest) { + return utils.deepAccess(bidRequest, 'schain'); +} - getOS: function () { - const w = window.top; - const ua = w.navigator.userAgent; - const uaLowerCase = ua.toLowerCase(); - return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : ''; +function _getEids(bidRequest) { + if (utils.deepAccess(bidRequest, 'userId')) { + return createEidsArray(bidRequest.userId); } } -function _pushInAdagioQueue(ob) { - try { - if (!canAccessTopWindow()) return; - const w = utils.getWindowTop(); - w.ADAGIO.queue.push(ob); - } catch (e) {} -}; +function _buildVideoBidRequest(bidRequest) { + const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const computedParams = {}; + + // Special case for playerSize. + // Eeach props will be overrided if they are defined in config. + 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]; + } -function _getOrAddAdagioAdUnit(adUnitCode) { - const w = utils.getWindowTop(); - if (w.ADAGIO.adUnits[adUnitCode]) { - return w.ADAGIO.adUnits[adUnitCode] + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + if (videoParams.context && videoParams.context === OUTSTREAM) { + bidRequest.mediaTypes.video.playerName = setPlayerName(bidRequest); } - return w.ADAGIO.adUnits[adUnitCode] = {}; + + // Only whitelisted OpenRTB options need to be validated. + // Other options will still remain in the `mediaTypes.video` object + // sent in the ad-request, but will be ignored by the SSP. + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + 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.`); + } + } + }); } -function _computePrintNumber(adUnitCode) { - let printNumber = 1; - const w = utils.getWindowTop(); - if ( - w.ADAGIO && - w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] && - w.ADAGIO.adUnits[adUnitCode].pageviewId === _getPageviewId() && - w.ADAGIO.adUnits[adUnitCode].printNumber - ) { - printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1; - } - return printNumber; +function _renderer(bid) { + bid.renderer.push(() => { + if (typeof window.ADAGIO.outstreamPlayer === 'function') { + window.ADAGIO.outstreamPlayer(bid); + } else { + utils.logError(`${LOG_PREFIX} Adagio outstream player is not defined`); + } + }); } -function _getDevice() { - const language = navigator.language ? 'language' : 'userLanguage'; - return { - userAgent: navigator.userAgent, - language: navigator[language], - deviceType: _features.getDevice(), - dnt: utils.getDNT() ? 1 : 0, - geo: {}, - js: 1 - }; -}; +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } -function _getSite() { - const w = utils.getWindowTop(); - return { - domain: w.location.hostname, - page: w.location.href, - referrer: w.document.referrer || '' - }; -}; + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } -function _getPageviewId() { - if (!canAccessTopWindow()) return false; - const w = utils.getWindowTop(); - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); - return w.ADAGIO.pageviewId; -}; + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); -function _getElementFromTopWindow(element, currentWindow) { - if (utils.getWindowTop() === currentWindow) { - if (!element.getAttribute('id')) { - element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; } - return element; + if (Array.isArray(bid.admNative.link.clicktrackers)) { + native.clickTrackers = bid.admNative.link.clicktrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); } else { - const frame = currentWindow.frameElement; - return _getElementFromTopWindow(frame, currentWindow.parent); + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } } -} -export function _autoDetectAdUnitElementId(adUnitCode) { - const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode) - let adUnitElementId = null; + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } - if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { - adUnitElementId = autoDetectedAdUnit.divId; + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } } - return adUnitElementId; + bid.native = native } -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 _getFloors(bidRequest) { + if (!utils.isFn(bidRequest.getFloor)) { + return false; + } + + const floors = []; + + const getAndPush = (mediaType, size) => { + const info = bidRequest.getFloor({ + currency: CURRENCY, + mediaType, + size: [] + }); + + floors.push(utils.cleanObj({ + mt: mediaType, + s: utils.isArray(size) ? `${size[0]}x${size[1]}` : undefined, + f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : DEFAULT_FLOOR + })); + } + + Object.keys(bidRequest.mediaTypes).forEach(mediaType => { + if (SUPPORTED_MEDIA_TYPES.indexOf(mediaType) !== -1) { + const sizeProp = mediaType === VIDEO ? 'playerSize' : 'sizes'; + + if (bidRequest.mediaTypes[mediaType][sizeProp] && bidRequest.mediaTypes[mediaType][sizeProp].length) { + if (utils.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 floors; } /** - * Returns all features for a specific adUnit element + * 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 {Object} bidRequest - * @returns {Object} features for an element (see specs) + * @param {*} bid + * @param {String} paramName */ -function _getFeatures(bidRequest) { - if (!canAccessTopWindow()) return; - const w = utils.getWindowTop(); - const adUnitCode = bidRequest.adUnitCode; - const adUnitElementId = bidRequest.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode); - let element; +export function setExtraParam(bid, paramName) { + bid.params = bid.params || {}; - if (!adUnitElementId) { - utils.logWarn('Unable to detect adUnitElementId. Adagio measures won\'t start'); - } else { - if (bidRequest.params.postBid === true) { - window.document.getElementById(adUnitElementId); - element = _getElementFromTopWindow(element, window); - w.ADAGIO.pbjsAdUnits.map((adUnit) => { - if (adUnit.code === adUnitCode) { - const outerElementId = element.getAttribute('id'); - adUnit.outerAdUnitElementId = outerElementId; - bidRequest.params.outerAdUnitElementId = outerElementId; - } - }); + // eslint-disable-next-line + if (!!(bid.params[paramName])) { + return; + } + + const adgGlobalConf = config.getConfig('adagio') || {}; + const ortb2Conf = config.getConfig('ortb2'); + + const detected = adgGlobalConf[paramName] || utils.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 = utils.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 { - element = w.document.getElementById(adUnitElementId); + bid.params.adUnitElementId = autoDetectAdUnitElementIdFromGpt(bid.adUnitCode); } } - const features = { - print_number: _features.getPrintNumber(bidRequest.adUnitCode).toString(), - page_dimensions: _features.getPageDimensions().toString(), - viewport_dimensions: _features.getViewPortDimensions().toString(), - dom_loading: _features.isDomLoading().toString(), - // layout: features.getLayout().toString(), - adunit_position: _features.getSlotPosition(element).toString(), - user_timestamp: _features.getTimestamp().toString(), - device: _features.getDevice().toString(), - url: w.location.origin + w.location.pathname, - browser: _features.getBrowser(), - os: _features.getOS() - }; + // extra params + setExtraParam(bid, 'environment'); + setExtraParam(bid, 'pagetype'); + setExtraParam(bid, 'category'); + setExtraParam(bid, 'subcategory'); +} - const adUnitFeature = {}; +function getPageDimensions() { + if (isSafeFrameWindow() || !canAccessTopWindow()) { + return ''; + } - adUnitFeature[adUnitElementId] = { - features: features, - version: FEATURES_VERSION - }; + // the page dimension can be computed on window.top only. + const wt = utils.getWindowTop(); + const body = wt.document.querySelector('body'); - _pushInAdagioQueue({ - action: 'features', - ts: Date.now(), - data: adUnitFeature - }); + 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 features; -}; + return `${pageWidth}x${pageHeight}`; +} -function _getGdprConsent(bidderRequest) { - const consent = {}; - if (utils.deepAccess(bidderRequest, 'gdprConsent')) { - if (bidderRequest.gdprConsent.consentString !== undefined) { - consent.consentString = bidderRequest.gdprConsent.consentString; +/** +* @todo Move to prebid Core as Utils. +* @returns +*/ +function 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, 'Unable to compute from safeframe api.'); + return ''; + } + + const sfGeom = ws.$sf.ext.geom(); + + if (!sfGeom || !sfGeom.win) { + utils.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 = utils.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 ''; + } + + const position = { x: 0, y: 0 }; + + if (isSafeFrameWindow()) { + const ws = utils.getWindowSelf(); + + if (typeof ws.$sf.ext.geom !== 'function') { + utils.logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); + return ''; + } + + const sfGeom = ws.$sf.ext.geom(); + + if (!sfGeom || !sfGeom.self) { + utils.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 = utils.getWindowTop(); + const d = wt.document; + + let domElement; + + if (utils.inIframe() === true) { + const ws = utils.getWindowSelf(); + const currentElement = ws.document.getElementById(adUnitElementId); + domElement = internal.getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(adUnitElementId); } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - consent.consentRequired = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + + if (!domElement) { + return ''; } - if (bidderRequest.gdprConsent.allowAuctionWithoutConsent !== undefined) { - consent.allowAuctionWithoutConsent = bidderRequest.gdprConsent.allowAuctionWithoutConsent ? 1 : 0; + + 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; } - if (bidderRequest.gdprConsent.apiVersion !== undefined) { - consent.apiVersion = bidderRequest.gdprConsent.apiVersion; + 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()) ? 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 consent; + + return domLoadingDuration; } -function _getSchain(bidRequest) { - if (utils.deepAccess(bidRequest, 'schain')) { - return bidRequest.schain; +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, - supportedMediaType: SUPPORTED_MEDIA_TYPES, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - isBidRequestValid: function (bid) { - const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid; - const { organizationId, site, placement } = bid.params; - const adUnitElementId = bid.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode); - const environment = bid.params.environment || _autoDetectEnvironment(); - let isValid = false; + isBidRequestValid(bid) { + bid.params = bid.params || {}; - utils.logInfo('adUnitElementId', adUnitElementId) + autoFillParams(bid); - try { - if (canAccessTopWindow()) { - const w = utils.getWindowTop(); - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - isValid = !!(organizationId && site && placement); - - const tempAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); - - bid.params = { - ...bid.params, - adUnitElementId, - environment - } + if (!internal.getRefererInfo().reachedTop) { + utils.logWarn(`${LOG_PREFIX} the main page url is unreachabled.`); + // internal.enqueue(debugData()); + return false; + } - tempAdUnits.push({ - code: adUnitCode, - sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, - bids: [{ - bidder, - params - }] - }); - w.ADAGIO.pbjsAdUnits = tempAdUnits; - - if (isValid === true) { - let printNumber = _computePrintNumber(adUnitCode); - w.ADAGIO.adUnits[adUnitCode] = { - auctionId: auctionId, - pageviewId: _getPageviewId(), - printNumber - }; - } - } - } catch (e) { - return isValid; + if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { + utils.logWarn(`${LOG_PREFIX} at least one required param is missing.`); + // internal.enqueue(debugData()); + return false; } - return isValid; - }, - buildRequests: function (validBidRequests, bidderRequest) { - // AdagioBidAdapter works when window.top can be reached only - if (!bidderRequest.refererInfo.reachedTop) return []; + return true; + }, + buildRequests(validBidRequests, bidderRequest) { const secure = (location.protocol === 'https:') ? 1 : 0; - const device = _getDevice(); - const site = _getSite(); - const pageviewId = _getPageviewId(); - const gdprConsent = _getGdprConsent(bidderRequest); + const device = internal.getDevice(); + const site = internal.getSite(bidderRequest); + const pageviewId = internal.getPageviewId(); + const gdprConsent = _getGdprConsent(bidderRequest) || {}; + const uspConsent = _getUspConsent(bidderRequest) || {}; + const coppa = _getCoppa(); const schain = _getSchain(validBidRequests[0]); + const eids = _getEids(validBidRequests[0]) || []; + const adUnits = utils._map(validBidRequests, (bidRequest) => { - bidRequest.features = _getFeatures(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 (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + _buildVideoBidRequest(bidRequest); + } + + storeRequestInAdagioNS(bidRequest); + return bidRequest; }); - // Regroug ad units by siteId + // Group ad units by organizationId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - if (adUnit.params && adUnit.params.organizationId) { - adUnit.params.organizationId = adUnit.params.organizationId.toString(); - } - (groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []).push(adUnit); + const adUnitCopy = utils.deepClone(adUnit); + adUnitCopy.params.organizationId = adUnitCopy.params.organizationId.toString(); + + // remove useless props + delete adUnitCopy.floorData; + delete adUnitCopy.params.siteId; + + groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || []; + groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy); + return groupedAdUnits; }, {}); - // Build one request per siteId - const requests = utils._map(Object.keys(groupedAdUnits), (organizationId) => { + // Build one request per organizationId + const requests = utils._map(Object.keys(groupedAdUnits), organizationId => { return { method: 'POST', url: ENDPOINT, @@ -480,28 +962,35 @@ export const spec = { site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - gdpr: gdprConsent, + data: GlobalExchange.getExchangeData(), + regs: { + gdpr: gdprConsent, + coppa: coppa, + ccpa: uspConsent + }, schain: schain, + user: { + eids: eids + }, prebidVersion: '$prebid.version$', - adapterVersion: VERSION, featuresVersion: FEATURES_VERSION }, options: { contentType: 'text/plain' } - } + }; }); return requests; }, - interpretResponse: function (serverResponse, bidRequest) { + interpretResponse(serverResponse, bidRequest) { let bidResponses = []; try { const response = serverResponse.body; if (response) { if (response.data) { - _pushInAdagioQueue({ + internal.enqueue({ action: 'ssp-data', ts: Date.now(), data: response.data @@ -510,7 +999,38 @@ export const spec = { if (response.bids) { response.bids.forEach(bidObj => { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + bidObj.meta = utils.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'); + // 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, '"')); + } + + if (mediaTypeContext === OUTSTREAM) { + bidObj.renderer = Renderer.install({ + id: bidObj.requestId, + adUnitCode: bidObj.adUnitCode, + url: bidObj.urlRenderer || RENDERER_URL, + config: { + ...utils.deepAccess(bidReq, 'mediaTypes.video'), + ...utils.deepAccess(bidObj, 'outstream', {}) + } + }); + + bidObj.renderer.setRender(_renderer); + } + } + + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; @@ -528,18 +1048,59 @@ export const spec = { return bidResponses; }, - getUserSyncs: function (syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses) { if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { return false; } - const syncs = serverResponses[0].body.userSyncs.map((sync) => { - return { - type: sync.t === 'p' ? 'image' : 'iframe', - url: sync.u - } - }) + + const syncs = serverResponses[0].body.userSyncs.map(sync => ({ + type: sync.t === 'p' ? 'image' : 'iframe', + url: sync.u + })); + 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 = utils.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 (utils.deepAccess(adagioBid, 'mediaTypes.video.context') === OUTSTREAM) { + adagioBid.params.playerName = setPlayerName(adagioBid); + } + + storeRequestInAdagioNS(adagioBid); + } + + return adagioBid.params; } -} +}; + +initAdagio(); registerBidder(spec); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index b34cc3fe37a..2779ced8cea 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -8,85 +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 + }, + 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 }, - bids: [{ - bidder: 'adagio', // Required - params: { - organizationId: '0', // Required - Organization ID provided by Adagio. - site: 'news-of-the-day', // Required - Site Name provided by Adagio. - placement: 'ban_atf', // 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: 'dfp_banniere_atf', // 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. - } - }] } - ]; - - 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; - } + }] + } +]; +``` + +## 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 + } + }, + bids: [{ + bidder: 'adagio', + params: { + placement: 'in_article', + adUnitElementId: 'article_outstream', + video: { + skip: 0 }, - { - key: "pagetype", - val: function (bidResponse) { - return bidResponse.pagetype; - } + debug: { + enabled: true, + cpm: 3.00 + } + } + }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 }, - { - key: "category", - val: function (bidResponse) { - return bidResponse.category; + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false } + } + } + }, + bids: [{ + bidder: 'adagio', + params: { + placement: 'in_article', + adUnitElementId: 'article_native', + native: { + context: 1, + plcmttype: 2 }, - { - key: "subcategory", - val: function (bidResponse) { - return bidResponse.subcategory; - } + debug: { + enabled: true, + cpm: 3.00 } - ] - } + } + }] } + ]; + + 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; + } + } + ] + } + } ``` diff --git a/modules/adblender.md b/modules/adblender.md new file mode 100644 index 00000000000..e70b2a4a8ed --- /dev/null +++ b/modules/adblender.md @@ -0,0 +1,36 @@ +# Overview + +Module Name: AdBlender Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@ad-blender.com + +# Description + +Connects to AdBlender demand source to fetch bids. +Banner and Video formats are supported. +Please use ```adblender``` as the bidder code. +#Bidder Config +You can set an alternate endpoint url `pbjs.setBidderConfig` for the bidder `adblender` +``` +pbjs.setBidderConfig({ + bidders: ["adblender"], + config: {"adblender": { "endpoint_url": "https://inv-nets.admixer.net/adblender.1.1.aspx"}} + }) +``` +# Ad Unit Example +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "adblender", + params: { + zone: 'fb3d34d0-7a88-4a4a-a5c9-8088cd7845f4' + } + } + ] + } + ]; +``` diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js new file mode 100644 index 00000000000..d3f3ba295b9 --- /dev/null +++ b/modules/adbookpspBidAdapter.js @@ -0,0 +1,796 @@ +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 * as utils 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 (utils.isPlainObject(deviceConfig)) { + return { ...device, ...deviceConfig }; + } + + return device; +} + +function buildRegs(bidderRequest) { + const regs = { + coppa: common.getConfig('coppa') === true ? 1 : 0, + }; + + if (bidderRequest.gdprConsent) { + utils.deepSetValue( + regs, + 'ext.gdpr', + bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + ); + utils.deepSetValue( + regs, + 'ext.gdprConsentString', + bidderRequest.gdprConsent.consentString || '' + ); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); + } + + return regs; +} + +function buildSource(bidRequests, bidderRequest) { + const source = { + fd: 1, + tid: bidderRequest.auctionId, + }; + const schain = utils.deepAccess(bidRequests, '0.schain'); + + if (schain) { + utils.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: + utils.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: utils.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 = utils.deepAccess(bidRequest, `params.video.${param}`); + const mediaTypeValue = utils.deepAccess( + bidRequest, + `mediaTypes.video.${param}` + ); + + if (paramsValue || mediaTypeValue) { + videoObj[param] = paramsValue || mediaTypeValue; + } + } + + return videoObj; +} + +function getVideoSize(bidRequest) { + const playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize', [[]]); + const { w, h } = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + + if (utils.isNumber(w) && utils.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) { + utils.deepSetValue(ext, 'adbook.placementId', placementId); + } + + if (effectiverOrgId) { + utils.deepSetValue(ext, 'adbook.orgId', effectiverOrgId); + } + + return ext; +} + +/** + * BID RESPONSE + */ + +function interpretResponse(bidResponse, bidderRequest) { + const bidderRequestBody = safeJSONparse(bidderRequest.data); + + if ( + utils.deepAccess(bidderRequestBody, 'id') != + utils.deepAccess(bidResponse, 'body.id') + ) { + utils.logError( + `${BIDDER_CODE}: Bid response id does not match bidder request id` + ); + + return []; + } + + const referrer = utils.deepAccess(bidderRequestBody, 'site.ref', ''); + const incomingBids = utils + .deepAccess(bidResponse, 'body.seatbid', []) + .filter((seat) => utils.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) => { + try { + const bidRequest = findBidRequest(bidderRequestBody, bid); + + if (!bidRequest) { + utils.logError(`${BIDDER_CODE}: Could not match bid to bid request`); + + return null; + } + const categories = utils.deepAccess(bid, 'cat', []); + const mediaType = getMediaType(bid.adm); + let prebidBid = { + ad: bid.adm, + adId: bid.adid, + adserverTargeting: targetingMap[bid.impid], + 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: utils.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), + }; + } + + return prebidBid; + } catch (error) { + utils.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(utils.uniques); + const values = impIds.reduce((result, id) => { + result[id] = { + lineItemIds: [], + dealIds: [], + adIds: [], + }; + + return result; + }, {}); + + bids.forEach((bid) => { + values[bid.impid].lineItemIds.push(bid.ext.liid); + values[bid.impid].dealIds.push(bid.dealid); + values[bid.impid].adIds.push(bid.adid); + }); + + const targetingMap = {}; + + for (const id of impIds) { + targetingMap[id] = { + hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), + hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), + hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), + }; + } + + return targetingMap; +} + +/** + * VALIDATION + */ + +function hasRequiredParams(bidRequest) { + const value = + utils.deepAccess(bidRequest, 'params.placementId') != null || + utils.deepAccess(bidRequest, 'params.orgId') != null || + getBidderConfig('orgId') != null; + + if (!value) { + utils.logError(`${BIDDER_CODE}: missing orgId and placementId parameter`); + } + + return value; +} + +function isValidBannerRequest(bidRequest) { + const value = validateSizes( + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes', []) + ); + + return value; +} + +function isValidVideoRequest(bidRequest) { + const value = + utils.isArray(utils.deepAccess(bidRequest, 'mediaTypes.video.mimes')) && + validateVideoSizes(bidRequest); + + return value; +} + +function validateSize(size) { + return utils.isArray(size) && size.length === 2 && size.every(utils.isNumber); +} + +function validateSizes(sizes) { + return utils.isArray(sizes) && sizes.length > 0 && sizes.every(validateSize); +} + +function validateVideoSizes(bidRequest) { + const { w, h } = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + + return ( + validateSizes( + utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') + ) || + (utils.isNumber(w) && utils.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) { + utils.logWarn(`${BIDDER_CODE}: Invalid bid`, bid); + } + + return value; + }; +} + +const commonBidValidators = [ + (bid) => utils.isPlainObject(bid), + (bid) => isNonEmptyStr(bid.adid), + (bid) => isNonEmptyStr(bid.adm), + (bid) => isNonEmptyStr(bid.id), + (bid) => isNonEmptyStr(bid.impid), + (bid) => isNonEmptyStr(utils.deepAccess(bid, 'ext.liid')), + (bid) => utils.isNumber(bid.price), +]; + +const bannerBidValidators = [ + validateBannerDimension('w'), + validateBannerDimension('h'), +]; + +function validateBannerDimension(dimension) { + return function (bid, bidRequest) { + if (bid[dimension] == null) { + return bannerHasSingleSize(bidRequest); + } + + return utils.isNumber(bid[dimension]); + }; +} + +function bannerHasSingleSize(bidRequest) { + return utils.deepAccess(bidRequest, 'banner.format', []).length === 1; +} + +/** + * USER SYNC + */ + +export const storage = getStorageManager(); + +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + return responses + .map((response) => utils.deepAccess(response, 'body.ext.sync')) + .filter(utils.isArray) + .reduce(utils.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) { + utils.triggerPixel(wurl); + } + + if (utils.isStr(bid.nurl)) { + utils.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 (_) { + utils.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 && utils.isPlainObject(markup.native)) { + return NATIVE; + } + + return BANNER; +} + +function safeJSONparse(...args) { + try { + return JSON.parse(...args); + } catch (_) { + return undefined; + } +} + +function isNonEmptyStr(value) { + return utils.isStr(value) && !utils.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 utils.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 47162aa2445..00000000000 --- a/modules/adbutlerBidAdapter.js +++ /dev/null @@ -1,131 +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'], - - 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 = {}; - - 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); - - 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) + ';'; - } - - 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/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md index 5905074270a..1921cc4046e 100644 --- a/modules/adbutlerBidAdapter.md +++ b/modules/adbutlerBidAdapter.md @@ -23,9 +23,12 @@ Module that connects to an AdButler zone to fetch bids. keyword: 'red', //optional minCPM: '1.00', //optional maxCPM: '5.00' //optional + extra: { // optional + foo: "bar" + } } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js new file mode 100644 index 00000000000..18cafe829b5 --- /dev/null +++ b/modules/addefendBidAdapter.js @@ -0,0 +1,79 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'addefend'; + +export const spec = { + code: BIDDER_CODE, + hostname: 'https://addefend-platform.com', + + getHostname() { + return this.hostname; + }, + isBidRequestValid: function(bid) { + return (bid.sizes !== undefined && bid.bidId !== undefined && bid.params !== undefined && + (bid.params.pageId !== undefined && (typeof bid.params.pageId === 'string')) && + (bid.params.placementId !== undefined && (typeof bid.params.placementId === 'string'))); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let bid = { + v: $$PREBID_GLOBAL$$.version, + auctionId: false, + pageId: false, + gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', + referer: bidderRequest.refererInfo.referer, + bids: [], + }; + + for (var i = 0; i < validBidRequests.length; i++) { + let vb = validBidRequests[i]; + let o = vb.params; + bid.auctionId = vb.auctionId; + o.bidId = vb.bidId; + o.transactionId = vb.transactionId; + o.sizes = []; + if (o.trafficTypes) { + bid.trafficTypes = o.trafficTypes; + } + delete o.trafficTypes; + + bid.pageId = o.pageId; + delete o.pageId; + + if (vb.sizes && Array.isArray(vb.sizes)) { + for (var j = 0; j < vb.sizes.length; j++) { + let s = vb.sizes[j]; + if (Array.isArray(s) && s.length == 2) { + o.sizes.push(s[0] + 'x' + s[1]); + } + } + } + bid.bids.push(o); + } + return [{ + method: 'POST', + url: this.getHostname() + '/bid', + options: { withCredentials: true }, + data: bid + }]; + }, + interpretResponse: function(serverResponse, request) { + const requiredKeys = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'advertiserDomains']; + const validBidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && (serverResponse.length > 0)) { + serverResponse.forEach((bid) => { + const bidResponse = {}; + for (const requiredKey of requiredKeys) { + if (!bid.hasOwnProperty(requiredKey)) { + return []; + } + bidResponse[requiredKey] = bid[requiredKey]; + } + validBidResponses.push(bidResponse); + }); + } + return validBidResponses; + } +} + +registerBidder(spec); diff --git a/modules/addefendBidAdapter.md b/modules/addefendBidAdapter.md new file mode 100644 index 00000000000..f1ac3ff2c46 --- /dev/null +++ b/modules/addefendBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdDefend Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@addefend.com +``` + +# Description + +Module that connects to AdDefend as a demand source. + +## Parameters +| Param | Description | Optional | Default | +| ------------- | ------------- | ----- | ----- | +| pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - | +| placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - | +| trafficTypes | comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
| yes | ADBLOCK | + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[970, 250]], // a display size + } + }, + bids: [ + { + bidder: "addefend", + params: { + pageId: "887", + placementId: "9398", + trafficTypes: "ADBLOCK" + } + } + ] + } + ]; +``` diff --git a/modules/adformOpenRTBBidAdapter.js b/modules/adfBidAdapter.js similarity index 58% rename from modules/adformOpenRTBBidAdapter.js rename to modules/adfBidAdapter.js index 3270fb5865a..43dcdcd1604 100644 --- a/modules/adformOpenRTBBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -5,13 +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 { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; -const BIDDER_CODE = 'adformOpenRTB'; +const { getConfig } = config; + +const BIDDER_CODE = 'adf'; const GVLID = 50; +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: { @@ -44,27 +51,59 @@ 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 ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: bid => !!bid.params.mid, 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) { + utils.mergeDeep(app, commonFpd.app); + } + } else { + site = getConfig('site') || {}; + if (commonFpd.site) { + utils.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 imp = { + id: id + 1, + tagid: bid.params.mid + }; + const assets = utils._map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; const asset = { @@ -100,21 +139,51 @@ export const spec = { } }).filter(Boolean); - return { - id: id + 1, - tagid: bid.params.mid, - native: { + if (assets.length) { + imp.native = { request: { assets } - } - }; + }; + + bid.mediaType = NATIVE; + return imp; + } + + const bannerParams = utils.deepAccess(bid, 'mediaTypes.banner'); + + if (bannerParams && bannerParams.sizes) { + const sizes = utils.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 + }; + bid.mediaType = BANNER; + + return imp; + } + + const videoParams = utils.deepAccess(bid, 'mediaTypes.video'); + if (videoParams) { + imp.video = videoParams; + bid.mediaType = VIDEO; + + 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, @@ -126,8 +195,8 @@ export const spec = { 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 } }; + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); } if (bidderRequest.uspConsent) { @@ -138,6 +207,10 @@ export const spec = { utils.deepSetValue(request, 'user.ext.eids', eids); } + if (schain) { + utils.deepSetValue(request, 'source.ext.schain', schain); + } + return { method: 'POST', url: 'https://' + adxDomain + '/adx/openrtb', @@ -162,17 +235,35 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - return { + const result = { requestId: bid.bidId, cpm: bidResponse.price, creativeId: bidResponse.crid, ttl: 360, netRevenue: bid.netRevenue === 'net', currency: cur, - mediaType: NATIVE, - bidderCode: BIDDER_CODE, - native: parseNative(bidResponse) + mediaType: bid.mediaType, + width: bidResponse.w, + height: bidResponse.h, + dealId: bidResponse.dealid, + meta: { + mediaType: bid.mediaType, + advertiserDomains: bidResponse.adomain + } }; + + if (bidResponse.native) { + result.native = parseNative(bidResponse); + } else { + result[ bid.mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; + } + + if (!bid.renderer && bid.mediaType === VIDEO && utils.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); } @@ -211,3 +302,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/adformOpenRTBBidAdapter.md b/modules/adfBidAdapter.md similarity index 51% rename from modules/adformOpenRTBBidAdapter.md rename to modules/adfBidAdapter.md index 0dd98ad07b8..10264e6f486 100644 --- a/modules/adformOpenRTBBidAdapter.md +++ b/modules/adfBidAdapter.md @@ -1,21 +1,18 @@ # Overview -Module Name: Adform OpenRTB Adapter +Module Name: Adf Adapter Module Type: Bidder Adapter 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. +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: { @@ -42,18 +39,38 @@ Only native format is supported. Using OpenRTB standard. } }, bids: [{ - bidder: 'adformOpenRTB', + 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 48000e082b2..00000000000 --- a/modules/adformBidAdapter.js +++ /dev/null @@ -1,186 +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'; - -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 ] ]; - var bids = JSON.parse(JSON.stringify(validBidRequests)); - var bidder = (bids[0] && bids[0].bidder) || BIDDER_CODE; - 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); - } - - if (eids) { - request.push('eids=' + eids); - } - - 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 encodeURIComponent(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; - }, {}); - } - }, - 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, - 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..f43fd284bad 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -2,6 +2,7 @@ import * as utils 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.1.0'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; @@ -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; 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.md b/modules/adhashBidAdapter.md new file mode 100644 index 00000000000..c1093e0ccd7 --- /dev/null +++ b/modules/adhashBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdHash Bidder Adapter +Module Type: Bidder Adapter +Maintainer: damyan@adhash.org +``` + +# Description + +Here is what you need for Prebid integration with AdHash: +1. Register with AdHash. +2. Once registered and approved, you will receive a Publisher ID and Platform URL. +3. Use the Publisher ID and Platform URL as parameters in params. + +Please note that a number of AdHash functionalities are not supported in the Prebid.js integration: +* Cookie-less frequency and recency capping; +* Audience segments; +* Price floors and passback tags, as they are not needed in the Prebid.js setup; +* Reservation for direct deals only, as bids are evaluated based on their price. + +# Test Parameters +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'adhash', + params: { + publisherId: '0x1234567890123456789012345678901234567890', + platformURL: 'https://adhash.org/p/struma/' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 3c129d1bee1..88f3e0e0e4f 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -4,10 +4,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'adhese'; +const GVLID = 553; const USER_SYNC_BASE_URL = 'https://user-sync.adhese.com/iframe/user_sync.html'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { @@ -20,21 +22,37 @@ export const spec = { } const { gdprConsent, refererInfo } = bidderRequest; + const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; + const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; + const commonParams = { ...gdprParams, ...refererParams }; + + const slots = validBidRequests.map(bid => ({ + slotname: bidToSlotName(bid), + parameters: cleanTargets(bid.params.data) + })); + + const payload = { + slots: slots, + parameters: commonParams, + vastContentAsUrl: true, + user: { + ext: { + eids: getEids(validBidRequests), + } + } + }; + const account = getAccount(validBidRequests); - const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {}); - const gdprParams = (gdprConsent && gdprConsent.consentString) ? [`xt${gdprConsent.consentString}`] : []; - const refererParams = (refererInfo && refererInfo.referer) ? [`xf${base64urlEncode(refererInfo.referer)}`] : []; - const id5Params = (getId5Id(validBidRequests)) ? [`x5${getId5Id(validBidRequests)}`] : []; - const targetsParams = Object.keys(targets).map(targetCode => targetCode + targets[targetCode].join(';')); - const slotsParams = validBidRequests.map(bid => 'sl' + bidToSlotName(bid)); - const params = [...slotsParams, ...targetsParams, ...gdprParams, ...refererParams, ...id5Params].map(s => `/${s}`).join(''); - const cacheBuster = '?t=' + new Date().getTime(); - const uri = 'https://ads-' + account + '.adhese.com/json' + params + cacheBuster; + const uri = 'https://ads-' + account + '.adhese.com/json'; return { - method: 'GET', + method: 'POST', url: uri, - bids: validBidRequests + data: JSON.stringify(payload), + bids: validBidRequests, + options: { + contentType: 'application/json' + } }; }, @@ -78,7 +96,7 @@ function adResponse(bid, ad) { const bidResponse = getbaseAdResponse({ requestId: bid.bidId, - mediaType: getMediaType(markup), + mediaType: ad.extension.mediaType, cpm: Number(price.amount), currency: price.currency, width: Number(ad.width), @@ -89,11 +107,18 @@ function adResponse(bid, ad) { originData: adDetails.originData, origin: adDetails.origin, originInstance: adDetails.originInstance + }, + meta: { + advertiserDomains: ad.adomain || [] } }); if (bidResponse.mediaType === VIDEO) { - bidResponse.vastXml = markup; + if (ad.cachedBodyUrl) { + bidResponse.vastUrl = ad.cachedBodyUrl + } else { + bidResponse.vastXml = markup; + } } else { const counter = ad.impressionCounter ? "" : ''; bidResponse.ad = markup + counter; @@ -101,16 +126,20 @@ function adResponse(bid, ad) { return bidResponse; } -function mergeTargets(targets, target) { +function cleanTargets(target) { + const targets = {}; if (target) { Object.keys(target).forEach(function (key) { const val = target[key]; - const values = Array.isArray(val) ? val : [val]; - if (targets[key]) { - const distinctValues = values.filter(v => targets[key].indexOf(v) < 0); - targets[key].push.apply(targets[key], distinctValues); - } else { - targets[key] = values; + const dirtyValues = Array.isArray(val) ? val : [val]; + const values = dirtyValues.filter(v => v === 0 || v); + if (values.length > 0) { + if (targets[key]) { + const distinctValues = values.filter(v => targets[key].indexOf(v) < 0); + targets[key].push.apply(targets[key], distinctValues); + } else { + targets[key] = values; + } } }); } @@ -137,9 +166,9 @@ function getAccount(validBidRequests) { return validBidRequests[0].params.account; } -function getId5Id(validBidRequests) { - if (validBidRequests[0] && validBidRequests[0].userId && validBidRequests[0].userId.id5id) { - return validBidRequests[0].userId.id5id; +function getEids(validBidRequests) { + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + return validBidRequests[0].userIdAsEids; } } @@ -148,12 +177,7 @@ function getbaseAdResponse(response) { } function isAdheseAd(ad) { - return !ad.origin || ad.origin === 'JERLICIA' || ad.origin === 'DALE'; -} - -function getMediaType(markup) { - const isVideo = markup.trim().toLowerCase().match(/<\?xml| { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn' + code: 'adkernelAdn', + gvlid: GVLID }); export default analyticsAdapter; @@ -390,3 +395,19 @@ function getLocationAndReferrer(win) { loc: win.location }; } + +function initPrivacy(template, requests) { + let consent = requests[0].gdprConsent; + if (consent && consent.gdprApplies) { + template.user.gdpr = ~~consent.gdprApplies; + } + if (consent && consent.consentString) { + template.user.gdpr_consent = consent.consentString; + } + if (requests[0].uspConsent) { + template.user.us_privacy = requests[0].uspConsent; + } + if (config.getConfig('coppa')) { + template.user.coppa = 1; + } +} diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 483d6de52b9..dc56ed6abbb 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,11 +1,13 @@ import * as utils 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 DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com'; const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; const DEFAULT_APIS = [1, 2]; +const GVLID = 14; function isRtbDebugEnabled(refInfo) { return refInfo.referer.indexOf('adk_debug=true') !== -1; @@ -16,13 +18,15 @@ function buildImp(bidRequest) { id: bidRequest.bidId, tagid: bidRequest.adUnitCode }; + let mediaType; let bannerReq = utils.deepAccess(bidRequest, `mediaTypes.banner`); let videoReq = utils.deepAccess(bidRequest, `mediaTypes.video`); if (bannerReq) { let sizes = canonicalizeSizesArray(bannerReq.sizes); imp.banner = { format: utils.parseSizesInput(sizes) - } + }; + mediaType = BANNER; } else if (videoReq) { let size = canonicalizeSizesArray(videoReq.playerSize)[0]; imp.video = { @@ -32,6 +36,11 @@ function buildImp(bidRequest) { protocols: videoReq.protocols || DEFAULT_PROTOCOLS, api: videoReq.api || DEFAULT_APIS }; + mediaType = VIDEO; + } + let bidFloor = getBidFloor(bidRequest, mediaType, '*'); + if (bidFloor) { + imp.bidfloor = bidFloor; } return imp; } @@ -48,11 +57,12 @@ function canonicalizeSizesArray(sizes) { return sizes; } -function buildRequestParams(tags, auctionId, transactionId, gdprConsent, uspConsent, refInfo) { +function buildRequestParams(tags, bidderRequest) { + let {auctionId, gdprConsent, uspConsent, transactionId, refererInfo} = bidderRequest; let req = { id: auctionId, tid: transactionId, - site: buildSite(refInfo), + site: buildSite(refererInfo), imp: tags }; if (gdprConsent) { @@ -66,6 +76,9 @@ function buildRequestParams(tags, auctionId, transactionId, gdprConsent, uspCons if (uspConsent) { utils.deepSetValue(req, 'user.us_privacy', uspConsent); } + if (config.getConfig('coppa')) { + utils.deepSetValue(req, 'user.coppa', 1); + } return req; } @@ -90,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; @@ -104,11 +121,46 @@ function buildBid(tag) { bid.vastUrl = tag.vast_url; bid.mediaType = VIDEO; } + fillBidMeta(bid, tag); return bid; } +function fillBidMeta(bid, tag) { + if (utils.isStr(tag.agencyName)) { + utils.deepSetValue(bid, 'meta.agencyName', tag.agencyName); + } + if (utils.isNumber(tag.advertiserId)) { + utils.deepSetValue(bid, 'meta.advertiserId', tag.advertiserId); + } + if (utils.isStr(tag.advertiserName)) { + utils.deepSetValue(bid, 'meta.advertiserName', tag.advertiserName); + } + if (utils.isArray(tag.advertiserDomains)) { + utils.deepSetValue(bid, 'meta.advertiserDomains', tag.advertiserDomains); + } + if (utils.isStr(tag.primaryCatId)) { + utils.deepSetValue(bid, 'meta.primaryCatId', tag.primaryCatId); + } + if (utils.isArray(tag.secondaryCatIds)) { + utils.deepSetValue(bid, 'meta.secondaryCatIds', tag.secondaryCatIds); + } +} + +function getBidFloor(bid, mediaType, sizes) { + var floor; + var size = sizes.length === 1 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({currency: 'USD', mediaType, size}); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } + return floor; +} + export const spec = { code: 'adkernelAdn', + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['engagesimply'], @@ -132,14 +184,13 @@ export const spec = { return acc; }, {}); - let {auctionId, gdprConsent, uspConsent, transactionId, refererInfo} = bidderRequest; let requests = []; Object.keys(dispatch).forEach(host => { Object.keys(dispatch[host]).forEach(pubId => { - let request = buildRequestParams(dispatch[host][pubId], auctionId, transactionId, gdprConsent, uspConsent, refererInfo); + let request = buildRequestParams(dispatch[host][pubId], bidderRequest); requests.push({ method: 'POST', - url: `https://${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(refererInfo) ? '&debug=1' : ''}`, + url: `https://${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(bidderRequest.refererInfo) ? '&debug=1' : ''}`, data: JSON.stringify(request) }) }); @@ -159,14 +210,24 @@ export const spec = { }, getUserSyncs: function(syncOptions, serverResponses) { - if (!syncOptions.iframeEnabled || !serverResponses || serverResponses.length === 0) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + if (syncOptions.iframeEnabled) { + return buildSyncs(serverResponses, 'syncpages', 'iframe'); + } else if (syncOptions.pixelEnabled) { + return buildSyncs(serverResponses, 'syncpixels', 'image'); + } else { return []; } - return serverResponses.filter(rps => rps.body && rps.body.syncpages) - .map(rsp => rsp.body.syncpages) - .reduce((a, b) => a.concat(b), []) - .map(syncUrl => ({type: 'iframe', url: syncUrl})); } }; +function buildSyncs(serverResponses, propName, type) { + return serverResponses.filter(rps => rps.body && rps.body[propName]) + .map(rsp => rsp.body[propName]) + .reduce((a, b) => a.concat(b), []) + .map(syncUrl => ({type: type, url: syncUrl})); +} + registerBidder(spec); diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index d069af7d56e..a6bf56f8cad 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -15,13 +15,14 @@ import {config} from '../src/config.js'; const VIDEO_TARGETING = Object.freeze(['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'linearity', 'boxingallowed', 'playbackmethod', 'delivery', 'pos', 'api', 'ext']); -const VERSION = '1.5'; +const VERSION = '1.6'; const SYNC_IFRAME = 1; const SYNC_IMAGE = 2; const SYNC_TYPES = Object.freeze({ 1: 'iframe', 2: 'image' }); +const GVLID = 14; const NATIVE_MODEL = [ {name: 'title', assetType: 'title'}, @@ -50,9 +51,28 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { * Adapter for requesting bids from AdKernel white-label display platform */ export const spec = { - code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon'], + gvlid: GVLID, + aliases: [ + {code: 'headbidding'}, + {code: 'adsolut'}, + {code: 'oftmediahb'}, + {code: 'audiencemedia'}, + {code: 'waardex_ak'}, + {code: 'roqoon'}, + {code: 'andbeyond'}, + {code: 'adbite'}, + {code: 'houseofpubs'}, + {code: 'torchad'}, + {code: 'stringads'}, + {code: 'bcm'}, + {code: 'engageadx'}, + {code: 'converge', gvlid: 248}, + {code: 'adomega'}, + {code: 'denakop'}, + {code: 'rtbanalytica'}, + {code: 'unibots'} + ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -78,10 +98,11 @@ export const spec = { */ buildRequests: function (bidRequests, bidderRequest) { let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); - const requests = []; + 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); + const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain); requests.push({ method: 'POST', url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, @@ -115,7 +136,7 @@ export const spec = { requestId: rtbBid.impid, cpm: rtbBid.price, creativeId: rtbBid.crid, - currency: 'USD', + currency: response.cur || 'USD', ttl: 360, netRevenue: true }; @@ -133,6 +154,27 @@ export const spec = { prBid.mediaType = NATIVE; prBid.native = buildNativeAd(JSON.parse(rtbBid.adm)); } + if (utils.isStr(rtbBid.dealid)) { + prBid.dealId = rtbBid.dealid; + } + if (utils.isArray(rtbBid.adomain)) { + utils.deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain); + } + if (utils.isArray(rtbBid.cat)) { + utils.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 (utils.isStr(rtbBid.ext.advertiser_name)) { + utils.deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name); + } + if (utils.isStr(rtbBid.ext.agency_name)) { + utils.deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); + } + } + return prBid; }); }, @@ -174,6 +216,18 @@ function dispatchImps(bidRequests, refererInfo) { }, {}); } +function getBidFloor(bid, mediaType, sizes) { + var floor; + var size = sizes.length === 1 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({currency: 'USD', mediaType, size}); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } + return floor; +} + /** * Builds rtb imp object for single adunit * @param bidRequest {BidRequest} @@ -184,27 +238,42 @@ function buildImp(bidRequest, secure) { 'id': bidRequest.bidId, 'tagid': bidRequest.adUnitCode }; + var mediaType; + var sizes = []; - if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = utils.getAdUnitSizes(bidRequest); + if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { + sizes = utils.getAdUnitSizes(bidRequest); imp.banner = { format: sizes.map(wh => utils.parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; + mediaType = BANNER; } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let sizes = bidRequest.mediaTypes.video.playerSize || []; - imp.video = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]) || {}; + let video = utils.deepAccess(bidRequest, 'mediaTypes.video'); + imp.video = {}; + if (video.playerSize) { + sizes = video.playerSize[0]; + imp.video = Object.assign(imp.video, utils.parseGPTSingleSizeArrayToRtbSize(sizes) || {}); + } if (bidRequest.params.video) { Object.keys(bidRequest.params.video) .filter(key => includes(VIDEO_TARGETING, key)) .forEach(key => imp.video[key] = bidRequest.params.video[key]); } + mediaType = VIDEO; } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { let nativeRequest = buildNativeRequest(bidRequest.mediaTypes.native); imp.native = { ver: '1.1', request: JSON.stringify(nativeRequest) - } + }; + mediaType = NATIVE; + } else { + throw new Error('Unsupported bid received'); + } + let floor = getBidFloor(bidRequest, mediaType, sizes); + if (floor) { + imp.bidfloor = floor; } if (secure) { imp.secure = 1; @@ -293,11 +362,12 @@ function getAllowedSyncMethod(bidderCode) { * Builds complete rtb request * @param imps {Object} Collection of rtb impressions * @param bidderRequest {BidderRequest} + * @param schain {Object=} Supply chain config * @return {Object} Complete rtb request */ -function buildRtbRequest(imps, bidderRequest) { +function buildRtbRequest(imps, bidderRequest, schain) { let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - + let coppa = config.getConfig('coppa'); let req = { 'id': auctionId, 'imp': imps, @@ -305,6 +375,7 @@ function buildRtbRequest(imps, bidderRequest) { 'at': 1, 'device': { 'ip': 'caller', + 'ipv6': 'caller', 'ua': 'caller', 'js': 1, 'language': getLanguage() @@ -325,10 +396,20 @@ function buildRtbRequest(imps, bidderRequest) { if (uspConsent) { utils.deepSetValue(req, 'regs.ext.us_privacy', uspConsent); } + if (coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { utils.deepSetValue(req, 'ext.adk_usersync', syncMethod); } + if (schain) { + utils.deepSetValue(req, 'source.ext.schain', schain); + } + let eids = getExtendedUserIds(bidderRequest); + if (eids) { + utils.deepSetValue(req, 'user.ext.eids', eids); + } return req; } @@ -360,6 +441,13 @@ function createSite(refInfo) { return site; } +function getExtendedUserIds(bidderRequest) { + let eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + if (utils.isArray(eids)) { + return eids; + } +} + /** * Format creative with optional nurl call * @param bid rtb Bid object 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/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js new file mode 100644 index 00000000000..2a61e59b714 --- /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 { EVENTS } from '../src/constants.json'; +import { targeting } from '../src/targeting.js'; +import * as utils from '../src/utils.js'; + +const MODULE = 'adlooxAdserverVideo'; + +const URL_VAST = 'https://j.adlooxtracking.com/ads/vast/tag.php'; + +export function buildVideoUrl(options, callback) { + utils.logInfo(MODULE, 'buildVideoUrl', options); + + if (!utils.isFn(callback)) { + utils.logError(MODULE, 'invalid callback'); + return false; + } + if (!utils.isPlainObject(options)) { + utils.logError(MODULE, 'missing options'); + return false; + } + if (!(options.url_vast === undefined || utils.isStr(options.url_vast))) { + utils.logError(MODULE, 'invalid url_vast options value'); + return false; + } + if (!(utils.isPlainObject(options.adUnit) || utils.isPlainObject(options.bid))) { + utils.logError(MODULE, "requires either 'adUnit' or 'bid' options value"); + return false; + } + if (!utils.isStr(options.url)) { + utils.logError(MODULE, 'invalid url options value'); + return false; + } + if (!(options.wrap === undefined || utils.isBoolean(options.wrap))) { + utils.logError(MODULE, 'invalid wrap options value'); + return false; + } + if (!(options.blob === undefined || utils.isBoolean(options.blob))) { + utils.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]; + + utils.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 = utils.deepClone(options.bid); + bid.ext.adloox.video.adserver = false; + + analyticsCommand(COMMAND.TRACK, { + eventType: EVENTS.BID_WON, + args: bid + }); +} + +function VASTWrapper(options, callback) { + const chain = []; + + function process(result) { + function getAd(xml) { + if (!xml || xml.documentElement.tagName != 'VAST') { + utils.logError(MODULE, 'not a VAST tag, using non-wrapped tracking'); + return; + } + + const ads = xml.querySelectorAll('Ad'); + if (!ads.length) { + utils.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 = utils.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) { + utils.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 ]); + + utils.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) { + utils.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) { + utils.logWarn(MODULE, `unable to download (${statusText}), suspected CORS withCredentials problem, retrying without`); + return fetch(url, true); + } + utils.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 new file mode 100644 index 00000000000..17787f816df --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.js @@ -0,0 +1,298 @@ +/** + * This module provides [Adloox]{@link https://www.adloox.com/} Analytics + * The module will inject Adloox's verification JS tag alongside slot at bidWin + * @module modules/adlooxAnalyticsAdapter + */ + +import adapterManager from '../src/adapterManager.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { AUCTION_COMPLETED } from '../src/auction.js'; +import { EVENTS } from '../src/constants.json'; +import find from 'core-js-pure/features/array/find.js'; +import * as utils from '../src/utils.js'; + +const MODULE = 'adlooxAnalyticsAdapter'; + +const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js'; + +const ADLOOX_VENDOR_ID = 93; + +const ADLOOX_MEDIATYPE = { + DISPLAY: 2, + VIDEO: 6 +}; + +const MACRO = {}; +MACRO['client'] = function(b, c) { + return c.client; +}; +MACRO['clientid'] = function(b, c) { + return c.clientid; +}; +MACRO['tagid'] = function(b, c) { + return c.tagid; +}; +MACRO['platformid'] = function(b, c) { + return c.platformid; +}; +MACRO['targetelt'] = function(b, c) { + return c.toselector(b); +}; +MACRO['creatype'] = function(b, c) { + return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; +}; +MACRO['pbadslot'] = function(b, c) { + const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); + return utils.deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; +}; +MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy + +const PARAMS_DEFAULT = { + 'id1': function(b) { return b.adUnitCode }, + 'id2': '%%pbadslot%%', + 'id3': function(b) { return b.bidder }, + 'id4': function(b) { return b.adId }, + 'id5': function(b) { return b.dealId }, + 'id6': function(b) { return b.creativeId }, + 'id7': function(b) { return b.size }, + 'id11': '$ADLOOX_WEBSITE' +}; + +const NOOP = function() {}; + +let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (!analyticsAdapter[`handle_${eventType}`]) return; + + utils.logInfo(MODULE, 'track', eventType, args); + + analyticsAdapter[`handle_${eventType}`](args); + } +}); + +analyticsAdapter.context = null; + +analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; +analyticsAdapter.enableAnalytics = function(config) { + analyticsAdapter.context = null; + + utils.logInfo(MODULE, 'config', config); + + if (!utils.isPlainObject(config.options)) { + utils.logError(MODULE, 'missing options'); + return; + } + if (!(config.options.js === undefined || utils.isStr(config.options.js))) { + utils.logError(MODULE, 'invalid js options value'); + return; + } + if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { + utils.logError(MODULE, 'invalid toselector options value'); + return; + } + if (!utils.isStr(config.options.client)) { + utils.logError(MODULE, 'invalid client options value'); + return; + } + if (!utils.isNumber(config.options.clientid)) { + utils.logError(MODULE, 'invalid clientid options value'); + return; + } + if (!utils.isNumber(config.options.tagid)) { + utils.logError(MODULE, 'invalid tagid options value'); + return; + } + if (!utils.isNumber(config.options.platformid)) { + utils.logError(MODULE, 'invalid platformid options value'); + return; + } + if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { + utils.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; + // https://mathiasbynens.be/notes/css-escapes + code = code.replace(/^\d/, '\\3$& '); + return `#${code}` + }, + client: config.options.client, + clientid: config.options.clientid, + tagid: config.options.tagid, + platformid: config.options.platformid, + params: [] + }; + + config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); + Object + .keys(config.options.params) + .forEach(k => { + if (!Array.isArray(config.options.params[k])) { + config.options.params[k] = [ config.options.params[k] ]; + } + config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ])); + }); + + Object.keys(COMMAND_QUEUE).forEach(commandProcess); + + analyticsAdapter.originEnableAnalytics(config); +} + +analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; +analyticsAdapter.disableAnalytics = function() { + analyticsAdapter.context = null; + + analyticsAdapter.originDisableAnalytics(); +} + +analyticsAdapter.url = function(url, args, bid) { + // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻ + function a2qs(a) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + } + + const args = []; + let n = a.length; + while (n-- > 0) { + if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) { + args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : '')); + } + } + + return args.join('&'); + } + + const macros = (str) => { + return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match); + }; + + url = macros(url); + args = args || []; + + let n = args.length; + while (n-- > 0) { + if (utils.isFn(args[n][1])) { + try { + args[n][1] = args[n][1](bid); + } catch (_) { + utils.logError(MODULE, 'macro', args[n][0], _.message); + args[n][1] = `ERROR: ${_.message}`; + } + } + if (utils.isStr(args[n][1])) { + args[n][1] = macros(args[n][1]); + } + } + + return url + a2qs(args); +} + +analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { + if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; + analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + + utils.logMessage(MODULE, 'preloading verification JS'); + + const uri = utils.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); +} + +analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { + if (utils.deepAccess(bid, 'ext.adloox.video.adserver')) { + utils.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`); + return; + } + + utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); + + const params = analyticsAdapter.context.params.concat([ + [ 'tagid', '%%tagid%%' ], + [ 'platform', '%%platformid%%' ], + [ 'fwtype', 4 ], + [ 'targetelt', '%%targetelt%%' ], + [ 'creatype', '%%creatype%%' ] + ]); + + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: analyticsAdapter, + code: 'adloox', + gvlid: ADLOOX_VENDOR_ID +}); + +export default analyticsAdapter; + +// src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻ +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 callback = function() { + delete COMMAND_QUEUE[cid]; + if (callback0) callback0.apply(null, arguments); + }; + COMMAND_QUEUE[cid] = { cmd, data, callback }; + if (analyticsAdapter.context) commandProcess(cid); +} +function commandProcess(cid) { + const { cmd, data, callback } = COMMAND_QUEUE[cid]; + + utils.logInfo(MODULE, 'command', cmd, data); + + switch (cmd) { + case COMMAND.CONFIG: + const response = { + client: analyticsAdapter.context.client, + clientid: analyticsAdapter.context.clientid, + tagid: analyticsAdapter.context.tagid, + platformid: analyticsAdapter.context.platformid + }; + 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)); + break; + case COMMAND.TRACK: + analyticsAdapter.track(data); + callback(); // drain queue + break; + default: + utils.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 new file mode 100644 index 00000000000..c4618a2e3aa --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.md @@ -0,0 +1,155 @@ +# Overview + + Module Name: Adloox Analytics Adapter + Module Type: Analytics Adapter + Maintainer: contact@adloox.com + +# Description + +Analytics adapter for adloox.com. Contact contact@adloox.com for information. + +This module can be used to track: + + * Display + * Native + * Video (see below for further instructions) + +The adapter adds an HTML `'; + ad += '' + return ad; +} function interpretResponse(serverResponse, bidRequest) { let bidResponses = []; if (serverResponse && serverResponse.body) { @@ -162,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) { @@ -176,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) { + utils.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); } @@ -211,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 { @@ -252,8 +308,9 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, - aliases: ['selectmediavideo'], - supportedMediaTypes: [VIDEO], + gvlid: GVLID, + aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo'], + 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 d7ff7453870..4ab6f53ebdc 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'; const AOL_BIDDERS_CODES = { AOL: 'aol', + VERIZON: 'verizon', ONEMOBILE: 'onemobile', ONEDISPLAY: 'onedisplay' }; @@ -29,6 +30,17 @@ const SYNC_TYPES = { } }; +const SUPPORTED_USER_ID_SOURCES = [ + 'adserver.org', + 'criteo.com', + 'id5-sync.com', + 'intentiq.com', + 'liveintent.com', + 'quantcast.com', + 'verizonmedia.com', + 'liveramp.com' +]; + const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; const nexageBaseApiTemplate = template`${'host'}/bidRequest?`; const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'dynamicParams'}`; @@ -48,10 +60,10 @@ const NUMERIC_VALUES = { }; function template(strings, ...keys) { - return function(...values) { + return function (...values) { let dict = values[values.length - 1] || {}; let result = [strings[0]]; - keys.forEach(function(key, i) { + keys.forEach(function (key, i) { let value = utils.isInteger(key) ? values[key] : dict[key]; result.push(value, strings[i + 1]); }); @@ -59,20 +71,22 @@ function template(strings, ...keys) { }; } -function _isMarketplaceBidder(bidder) { - return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEDISPLAY; +function _isMarketplaceBidder(bidderCode) { + return bidderCode === AOL_BIDDERS_CODES.AOL || + bidderCode === AOL_BIDDERS_CODES.VERIZON || + bidderCode === AOL_BIDDERS_CODES.ONEDISPLAY; } function _isOneMobileBidder(bidderCode) { - return bidderCode === AOL_BIDDERS_CODES.AOL || bidderCode === AOL_BIDDERS_CODES.ONEMOBILE; + return bidderCode === AOL_BIDDERS_CODES.AOL || + bidderCode === AOL_BIDDERS_CODES.VERIZON || + bidderCode === AOL_BIDDERS_CODES.ONEMOBILE; } 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; } } @@ -98,10 +112,20 @@ function resolveEndpointCode(bid) { } } +function getSupportedEids(bid) { + return bid.userIdAsEids.filter(eid => { + return SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1 + }); +} + export const spec = { code: AOL_BIDDERS_CODES.AOL, gvlid: 25, - aliases: [AOL_BIDDERS_CODES.ONEMOBILE, AOL_BIDDERS_CODES.ONEDISPLAY], + aliases: [ + AOL_BIDDERS_CODES.ONEMOBILE, + AOL_BIDDERS_CODES.ONEDISPLAY, + AOL_BIDDERS_CODES.VERIZON + ], supportedMediaTypes: [BANNER], isBidRequestValid(bid) { return isMarketplaceBid(bid) || isMobileBid(bid); @@ -121,7 +145,7 @@ export const spec = { } }); }, - interpretResponse({body}, bidRequest) { + interpretResponse({ body }, bidRequest) { if (!body) { utils.logError('Empty bid response', bidRequest.bidderCode, body); } else { @@ -216,11 +240,18 @@ export const spec = { })); }, buildOneMobileGetUrl(bid, consentData) { - let {dcn, pos, ext} = bid.params; + let { dcn, pos, ext } = bid.params; + if (typeof bid.userId === 'object') { + ext = ext || {}; + let eids = getSupportedEids(bid); + eids.forEach(eid => { + ext['eid' + eid.source] = eid.uids[0].id; + }); + } let nexageApi = this.buildOneMobileBaseUrl(bid); if (dcn && pos) { let dynamicParams = this.formatOneMobileDynamicParams(ext, consentData); - nexageApi += nexageGetApiTemplate({dcn, pos, dynamicParams}); + nexageApi += nexageGetApiTemplate({ dcn, pos, dynamicParams }); } return nexageApi; }, @@ -238,10 +269,6 @@ 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)); @@ -283,6 +310,16 @@ export const spec = { utils.deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); } + if (typeof bid.userId === 'object') { + openRtbObject.user = openRtbObject.user || {}; + openRtbObject.user.ext = openRtbObject.user.ext || {}; + + let eids = getSupportedEids(bid); + if (eids.length > 0) { + openRtbObject.user.ext.eids = eids + } + } + return openRtbObject; }, isEUConsentRequired(consentData) { @@ -327,7 +364,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 @@ -374,6 +411,9 @@ export const spec = { currency: response.cur || 'USD', dealId: bidData.dealid, netRevenue: true, + meta: { + advertiserDomains: bidData && bidData.adomain ? bidData.adomain : [] + }, ttl: bidRequest.ttl }; }, diff --git a/modules/quantumdexBidAdapter.js b/modules/apacdexBidAdapter.js similarity index 56% rename from modules/quantumdexBidAdapter.js rename to modules/apacdexBidAdapter.js index 9b7a79755e3..6c1c350b800 100644 --- a/modules/quantumdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,7 +1,12 @@ import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'quantumdex'; +const BIDDER_CODE = 'apacdex'; const CONFIG = { + 'apacdex': { + 'ENDPOINT': 'https://useast.quantumdex.io/auction/apacdex', + 'USERSYNC': 'https://sync.quantumdex.io/usersync/apacdex' + }, 'quantumdex': { 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex', 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex' @@ -12,25 +17,25 @@ const CONFIG = { } }; -var bidderConfig = CONFIG['quantumdex']; +var bidderConfig = CONFIG[BIDDER_CODE]; var bySlotTargetKey = {}; var bySlotSizesCount = {} export const spec = { code: BIDDER_CODE, supportedMediaTypes: ['banner', 'video'], - aliases: ['valueimpression'], + aliases: ['quantumdex', 'valueimpression'], isBidRequestValid: function (bid) { 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')) { return false; } - if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Quantumdex does not support multi type bids, favor banner over video + if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Not support multi type bids, favor banner over video if (!utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { // sizes at the banner is required. return false; @@ -45,11 +50,31 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - var bids = JSON.parse(JSON.stringify(validBidRequests)) - bidderConfig = CONFIG[bids[0].bidder]; - const payload = {}; + let schain; + let eids; + let geo; + let test; + let bids = []; + + bidderConfig = CONFIG[validBidRequests[0].bidder]; + + test = config.getConfig('debug'); + + validBidRequests.forEach(bidReq => { + if (bidReq.schain) { + schain = schain || bidReq.schain + } + + if (bidReq.userIdAsEids) { + eids = eids || bidReq.userIdAsEids + } + + if (bidReq.params && bidReq.params.geo) { + if (validateGeoObject(bidReq.params.geo)) { + geo = bidReq.params.geo; + } + } - bids.forEach(bidReq => { var targetKey = 0; if (bySlotTargetKey[bidReq.adUnitCode] != undefined) { targetKey = bySlotTargetKey[bidReq.adUnitCode]; @@ -67,39 +92,73 @@ 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 = {}; + payload.tmax = bidderRequest.timeout; + if (test) { + payload.test = 1; + } + payload.device = {}; payload.device.ua = navigator.userAgent; - payload.device.height = window.top.innerHeight; - payload.device.width = window.top.innerWidth; + payload.device.height = window.screen.width; + payload.device.width = window.screen.height; payload.device.dnt = _getDoNotTrack(); payload.device.language = navigator.language; + var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.id = bids[0].params.siteId; - payload.site.page = _extractTopWindowUrlFromBidderRequest(bidderRequest); + payload.site.page = pageUrl payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); - payload.site.hostname = window.top.location.hostname; + payload.site.hostname = getDomain(pageUrl); // Apply GDPR parameters to request. - payload.gdpr = {}; if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr.gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 'true' : 'false'; + payload.gdpr = {}; + payload.gdpr.gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; if (bidderRequest.gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } } - // Apply schain. - if (bids[0].schain) { - payload.schain = JSON.stringify(bids[0].schain) - } + // Apply us_privacy. if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } - payload.bids = bids; + // Apply schain. + if (schain) { + payload.schain = schain + } + + // Apply eids. + if (eids) { + payload.eids = eids + } + + // Apply geo + if (geo) { + payload.geo = geo; + } + + 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', @@ -111,34 +170,41 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; - const serverBids = serverBody.bids; - // check overall response - if (!serverBody || typeof serverBody !== 'object') { + if (!serverBody || !utils.isPlainObject(serverBody)) { return []; } - if (!serverBids || typeof serverBids !== 'object') { + + const serverBids = serverBody.bids; + if (!serverBids || !utils.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); } else { bidResponse.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); } + bidResponse.meta = {}; + if (bid.meta && bid.meta.advertiserDomains && utils.isArray(bid.meta.advertiserDomains)) { + bidResponse.meta.advertiserDomains = bid.meta.advertiserDomains; + } bidResponses.push(bidResponse); }); return bidResponses; @@ -188,15 +254,25 @@ function _getBiggestSize(sizes) { } function _getDoNotTrack() { - if (window.top.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { - if (window.top.doNotTrack == '1' || navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') { + try { + if (window.top.doNotTrack && window.top.doNotTrack == '1') { return 1; - } else { - return 0; } - } else { - return 0; - } + } catch (e) { } + + try { + if (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) { + return 1; + } + } catch (e) { } + + try { + if (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') { + return 1; + } + } catch (e) { } + + return 0 } /** @@ -206,8 +282,11 @@ function _getDoNotTrack() { * @returns {string} */ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { - if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { - return bidderRequest.refererInfo.canonicalUrl; + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + if (utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; } try { @@ -235,4 +314,67 @@ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { } } +/** + * Extracts the domain from given page url + * + * @param {string} url + * @returns {string} + */ +export function getDomain(pageUrl) { + if (config.getConfig('publisherDomain')) { + var publisherDomain = config.getConfig('publisherDomain'); + return publisherDomain.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; + } + + if (!pageUrl) { + return pageUrl; + } + + return pageUrl.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; +} + +/** + * Validate geo object + * + * @param {Object} geo + * @returns {boolean} + */ +export function validateGeoObject(geo) { + if (!utils.isPlainObject(geo)) { + return false; + } + if (!geo.lat) { + return false; + } + if (!geo.lon) { + return false; + } + if (!geo.accuracy) { + return false; + } + return true; +} + +/** + * Get bid floor from Price Floors Module + * + * @param {Object} bid + * @returns {float||null} + */ +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return (bid.params.floorPrice) ? bid.params.floorPrice : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.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 new file mode 100644 index 00000000000..fa8c727d709 --- /dev/null +++ b/modules/apacdexBidAdapter.md @@ -0,0 +1,104 @@ +# Overview + +``` +Module Name: APAC Digital Exchange Bidder Adapter +Module Type: Bidder Adapter +Maintainer: ken@apacdex.com +``` + +# Description + +Connects to APAC Digital Exchange for bids. +Apacdex bid adapter supports Banner and Video (Instream and Outstream) ads. + +# Sample Banner Ad Unit +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [ + { + bidder: 'apacdex', + params: { + siteId: 'apacdex1234', // siteId provided by Apacdex + floorPrice: 0.01, // default is 0.01 if not declared + } + } + ] + } +]; +``` + +# Sample Video Ad Unit: Instream +``` +var videoAdUnit = { + code: 'test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + 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: [ + { + 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 + +# 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/appnexusAnalyticsAdapter.js b/modules/appnexusAnalyticsAdapter.js index d697d31cdd3..868b317d7d4 100644 --- a/modules/appnexusAnalyticsAdapter.js +++ b/modules/appnexusAnalyticsAdapter.js @@ -13,7 +13,8 @@ var appnexusAdapter = adapter({ adapterManager.registerAnalyticsAdapter({ adapter: appnexusAdapter, - code: 'appnexus' + code: 'appnexus', + gvlid: 32 }); export default appnexusAdapter; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index df0b60cb7d7..9882e71fe4f 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -11,8 +11,10 @@ import { getStorageManager } from '../src/storageManager.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']; @@ -53,9 +55,9 @@ const NATIVE_MAPPING = { }; const SOURCE = 'pbjs'; const MAX_IMPS_PER_REQUEST = 15; -const mappingFileUrl = 'https://acdn.adnxs.com/prebid/appnexus-mapping/mappings.json'; +const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/mappings.json'; const SCRIPT_TAG_START = ' includes(USER_PARAMS, param)) .forEach((param) => { let uparam = utils.convertCamelToUnderscore(param); - userObj[uparam] = userObjBid.params.user[param] + if (param === 'segments' && utils.isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (utils.isNumber(val)) { + segs.push({'id': val}); + } else if (utils.isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } }); } @@ -144,6 +172,7 @@ export const spec = { const memberIdBid = find(bidRequests, hasMemberId); const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; const schain = bidRequests[0].schain; + const omidSupport = find(bidRequests, hasOmidSupport); const payload = { tags: [...tags], @@ -155,6 +184,13 @@ export const spec = { schain: schain }; + if (omidSupport) { + payload['iab_support'] = { + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + } + } + if (member > 0) { payload.member_id = member; } @@ -181,6 +217,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) { @@ -207,26 +250,19 @@ export const spec = { }); } - let eids = []; - const criteoId = utils.deepAccess(bidRequests[0], `userId.criteoId`); - if (criteoId) { - eids.push({ - source: 'criteo.com', - id: criteoId - }); - } + if (bidRequests[0].userId) { + let eids = []; - const tdid = utils.deepAccess(bidRequests[0], `userId.tdid`); - if (tdid) { - eids.push({ - source: 'adserver.org', - id: tdid, - rti_partner: 'TDID' - }); - } + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); + 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, utils.deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); - if (eids.length) { - payload.eids = eids; + if (eids.length) { + payload.eids = eids; + } } if (tags[0].publisher_id) { @@ -302,8 +338,8 @@ export const spec = { } }, - getUserSyncs: function (syncOptions) { - if (syncOptions.iframeEnabled) { + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' @@ -458,10 +494,19 @@ function hasPurpose1Consent(bidderRequest) { function formatRequest(payload, bidderRequest) { let request = []; - let options = {}; + let options = { + withCredentials: true + }; + + let endpointUrl = URL; + if (!hasPurpose1Consent(bidderRequest)) { - options = { - withCredentials: false + endpointUrl = URL_SIMPLE; + } + + if (utils.getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { + 'X-Is-Test': 1 } } @@ -473,7 +518,7 @@ function formatRequest(payload, bidderRequest) { const payloadString = JSON.stringify(clonedPayload); request.push({ method: 'POST', - url: URL, + url: endpointUrl, data: payloadString, bidderRequest, options @@ -483,7 +528,7 @@ function formatRequest(payload, bidderRequest) { const payloadString = JSON.stringify(payload); request = { method: 'POST', - url: URL, + url: endpointUrl, data: payloadString, bidderRequest, options @@ -544,6 +589,11 @@ 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 }); } @@ -673,8 +723,9 @@ 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; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + tag.reserve = bidFloor; } if (bid.params.position) { tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; @@ -709,6 +760,11 @@ function bidToTag(bid) { tag.keywords = keywords; } + let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + tag.gpid = gpid; + } + if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { tag.ad_types.push(NATIVE); if (tag.sizes.length === 0) { @@ -751,16 +807,72 @@ function bidToTag(bid) { type = (utils.isArray(type)) ? type[0] : type; tag.video[param] = VIDEO_MAPPING[param][type]; break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; default: tag.video[param] = bid.params.video[param]; } }); + + if (bid.params.video.frameworks && utils.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 = (utils.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'] && utils.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)) { + 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); @@ -829,6 +941,19 @@ function hasAdPod(bid) { ); } +function hasOmidSupport(bid) { + let hasOmid = false; + const bidderParams = bid.params; + const videoParams = bid.params.video; + if (bidderParams.frameworks && utils.isArray(bidderParams.frameworks)) { + hasOmid = includes(bid.params.frameworks, 6); + } + if (!hasOmid && videoParams && videoParams.frameworks && utils.isArray(videoParams.frameworks)) { + hasOmid = includes(bid.params.video.frameworks, 6); + } + return hasOmid; +} + /** * Expand an adpod placement into a set of request objects according to the * total adpod duration and the range of duration seconds. Sets minduration/ @@ -928,9 +1053,22 @@ function hidedfpContainer(elementId) { } } +function hideSASIframe(elementId) { + try { + // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. + const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { + el[0].nextSibling.style.setProperty('display', 'none'); + } + } catch (e) { + // element not found! + } +} + function outstreamRender(bid) { - // push to render queue because ANOutstreamVideo may not be loaded yet hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); + // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, @@ -958,4 +1096,31 @@ function parseMediaType(rtbBid) { } } +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 (!utils.isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/appnexusBidAdapter.md b/modules/appnexusBidAdapter.md index 6ec40e83b41..7ac70e67584 100644 --- a/modules/appnexusBidAdapter.md +++ b/modules/appnexusBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Appnexus Bid Adapter Module Type: Bidder Adapter -Maintainer: info@prebid.org +Maintainer: prebid-js@xandr.com ``` # Description @@ -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 new file mode 100644 index 00000000000..4fb89b9c720 --- /dev/null +++ b/modules/apstreamBidAdapter.js @@ -0,0 +1,490 @@ +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 = { + DSU_KEY: 'apr_dsu', + BIDDER_CODE: 'apstream', + GVLID: 394 +}; +const storage = getStorageManager(CONSTANTS.GVLID, CONSTANTS.BIDDER_CODE); + +var dsuModule = (function() { + 'use strict'; + + var DSU_KEY = 'apr_dsu'; + var DSU_VERSION_NUMBER = '1'; + var SIGNATURE_SALT = 'YicAu6ZpNG'; + var DSU_CREATOR = {'USERREPORT': '1'}; + + function stringToU8(str) { + if (typeof TextEncoder === 'function') { + return new TextEncoder().encode(str); + } + str = unescape(encodeURIComponent(str)); + var bytes = new Uint8Array(str.length); + for (var i = 0, j = str.length; i < j; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes; + } + + function _add(a, b) { + var rl = a.l + b.l; + var a2 = { + h: a.h + b.h + (rl / 2 >>> 31) >>> 0, + l: rl >>> 0 + }; + a.h = a2.h; + a.l = a2.l; + } + function _xor(a, b) { + a.h ^= b.h; + a.h >>>= 0; + a.l ^= b.l; + a.l >>>= 0; + } + function _rotl(a, n) { + var a2 = { + h: a.h << n | a.l >>> (32 - n), + l: a.l << n | a.h >>> (32 - n) + }; + a.h = a2.h; + a.l = a2.l; + } + function _rotl32(a) { + var al = a.l; + a.l = a.h; + a.h = al; + } + + function _compress(v0, v1, v2, v3) { + _add(v0, v1); + _add(v2, v3); + _rotl(v1, 13); + _rotl(v3, 16); + _xor(v1, v0); + _xor(v3, v2); + _rotl32(v0); + _add(v2, v1); + _add(v0, v3); + _rotl(v1, 17); + _rotl(v3, 21); + _xor(v1, v2); + _xor(v3, v0); + _rotl32(v2); + } + function _getInt(a, offset) { + return a[offset + 3] << 24 | + a[offset + 2] << 16 | + a[offset + 1] << 8 | + a[offset]; + } + + function hash(key, m) { + if (typeof m === 'string') { + m = stringToU8(m); + } + var k0 = { + h: key[1] >>> 0, + l: key[0] >>> 0 + }; + var k1 = { + h: key[3] >>> 0, + l: key[2] >>> 0 + }; + var v0 = { + h: k0.h, + l: k0.l + }; + var v2 = k0; + var v1 = { + h: k1.h, + l: k1.l + }; + var v3 = k1; + var ml = m.length; + var ml7 = ml - 7; + var buf = new Uint8Array(new ArrayBuffer(8)); + _xor(v0, { + h: 0x736f6d65, + l: 0x70736575 + }); + _xor(v1, { + h: 0x646f7261, + l: 0x6e646f6d + }); + _xor(v2, { + h: 0x6c796765, + l: 0x6e657261 + }); + _xor(v3, { + h: 0x74656462, + l: 0x79746573 + }); + var mp = 0; + while (mp < ml7) { + var mi = { + h: _getInt(m, mp + 4), + l: _getInt(m, mp) + }; + _xor(v3, mi); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _xor(v0, mi); + mp += 8; + } + buf[7] = ml; + var ic = 0; + while (mp < ml) { + buf[ic++] = m[mp++]; + } + while (ic < 7) { + buf[ic++] = 0; + } + var mil = { + h: buf[7] << 24 | buf[6] << 16 | buf[5] << 8 | buf[4], + l: buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0] + }; + _xor(v3, mil); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _xor(v0, mil); + _xor(v2, { + h: 0, + l: 0xff + }); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + var h = v0; + _xor(h, v1); + _xor(h, v2); + _xor(h, v3); + return h; + } + + function hashHex(key, m) { + var r = hash(key, m); + return ('0000000' + r.h.toString(16)).substr(-8) + + ('0000000' + r.l.toString(16)).substr(-8); + } + + var SIPHASH_KEY = [0x86395a57, 0x6b5ba7f7, 0x69732c07, 0x2a6ef48d]; + var hashWithKey = hashHex.bind(null, SIPHASH_KEY); + + var parseUrlRegex = new RegExp('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?'); + var overwrite = null; + var cache = {}; + function parseUrl(url) { + var addscheme = + url.indexOf('/') !== 0 && + url.indexOf('/') !== -1 && + (url.indexOf(':') === -1 || url.indexOf(':') > url.indexOf('/')); + + var match = parseUrlRegex.exec(addscheme ? 'noscheme://' + url : url); + var res = { + scheme: addscheme ? '' : match[2] || '', + host: match[4] || '', + hostname: match[4] ? match[4].split(':')[0] : '', + pathname: match[5] || '', + search: match[7] || '', + hash: match[9] || '', + toString: function () { + return url; + } + }; + + res.origin = res.scheme + '://' + res.host; + return res; + } + + function location() { + var url = overwrite || window.location.toString(); + url = url.replace(/\.demo\.audienceproject\.com\//, '/'); + + if (cache.url === url) { + return cache.parsed; + } + var parsed = parseUrl(url); + cache.url = url; + cache.parsed = parsed; + return parsed; + } + + function getDaysSinceApEpoch() { + var timeDiff = (new Date()).getTime() - (new Date(2019, 0, 1)).getTime(); + var daysSinceApEpoch = Math.floor(timeDiff / (1000 * 3600 * 24)); + return daysSinceApEpoch; + } + + function generateDsu() { + var dsuId = utils.generateUUID(); + var loc = location(); + + var dsuIdSuffix = hashWithKey(dsuId + loc.toString()); + var suffix4 = dsuIdSuffix.substr(0, 4); + var suffix8 = dsuIdSuffix.substr(4); + + dsuId = dsuId.substr(0, 19) + suffix4 + '-' + suffix8; + + var daysSinceApEpoch = getDaysSinceApEpoch(); + var originHash = hashWithKey(loc.origin); + + var metadata = [ + DSU_CREATOR.USERREPORT, + daysSinceApEpoch, + originHash + ].join('.'); + var signature = hashWithKey(dsuId + metadata + SIGNATURE_SALT); + + return [DSU_VERSION_NUMBER, signature, dsuId, metadata].join('.'); + } + + function readOrCreateDsu() { + var dsu; + try { + dsu = storage.getDataFromLocalStorage(DSU_KEY); + } catch (err) { + return null; + } + + if (!dsu) { + dsu = generateDsu(); + } + + try { + storage.setDataInLocalStorage(DSU_KEY, dsu); + } catch (err) { + return null; + } + + return dsu; + } + + return { + readOrCreateDsu: readOrCreateDsu + } +})(); + +function serializeSizes(sizes) { + if (Array.isArray(sizes[0]) === false) { + sizes = [sizes]; + } + + return sizes.map(s => s[0] + 'x' + s[1]).join('_'); +} + +function getRawConsentString(gdprConsentConfig) { + if (!gdprConsentConfig || gdprConsentConfig.gdprApplies === false) { + return null; + } + + return gdprConsentConfig.consentString; +} + +function getConsentStringFromPrebid(gdprConsentConfig) { + const consentString = getRawConsentString(gdprConsentConfig); + if (!consentString) { + return null; + } + + let isIab = config.getConfig('consentManagement.cmpApi') != 'static'; + let vendorConsents = ( + gdprConsentConfig.vendorData.vendorConsents || + (gdprConsentConfig.vendorData.vendor || {}).consents || + {} + ); + let isConsentGiven = !!vendorConsents[CONSTANTS.GVLID.toString(10)]; + + return isIab && isConsentGiven ? consentString : null; +} + +function getIabConsentString(bidderRequest) { + if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + return getConsentStringFromPrebid(bidderRequest.gdprConsent); + } + + return 'disabled'; +} + +function injectPixels(ad, pixels, scripts) { + if (!pixels && !scripts) { + return ad; + } + + let trackedAd = ad; + if (pixels) { + pixels.forEach(pixel => { + const tracker = utils.createTrackPixelHtml(pixel); + trackedAd += tracker; + }); + } + + if (scripts) { + scripts.forEach(script => { + const tracker = ``; + trackedAd += tracker; + }); + } + + return trackedAd; +} + +function getScreenParams() { + return `${window.screen.width}x${window.screen.height}@${window.devicePixelRatio}`; +} + +function getBids(bids) { + const bidArr = bids.map(bid => { + const bidId = bid.bidId; + + let mediaType = ''; + const mediaTypes = Object.keys(bid.mediaTypes) + switch (mediaTypes[0]) { + case 'video': + mediaType = 'v'; + break; + + case 'native': + mediaType = 'n'; + break; + + case 'audio': + mediaType = 'a'; + break; + + default: + mediaType = 'b'; + break; + } + + let adUnitCode = `,c=${bid.adUnitCode}`; + if (bid.params.code) { + adUnitCode = `,c=${encodeURIComponent(bid.params.code)}`; + } + if (bid.params.adunitId) { + adUnitCode = `,u=${encodeURIComponent(bid.params.adunitId)}`; + } + + return `${bidId}:t=${mediaType},s=${serializeSizes(bid.sizes)}${adUnitCode}`; + }); + + return bidArr.join(';'); +}; + +function getEndpointsGroups(bidRequests) { + let endpoints = []; + const getEndpoint = bid => { + const publisherId = bid.params.publisherId || config.getConfig('apstream.publisherId'); + const isTestConfig = bid.params.test || config.getConfig('apstream.test'); + + if (isTestConfig) { + return `https://mock-bapi.userreport.com/v2/${publisherId}/bid`; + } + + if (bid.params.endpoint) { + return `${bid.params.endpoint}${publisherId}/bid`; + } + + return `https://bapi.userreport.com/v2/${publisherId}/bid`; + } + bidRequests.forEach(bid => { + const endpoint = getEndpoint(bid); + const exist = endpoints.filter(item => item.endpoint.indexOf(endpoint) > -1)[0]; + if (exist) { + exist.bids.push(bid); + } else { + endpoints.push({ + endpoint: endpoint, + bids: [bid] + }); + } + }); + + return endpoints; +} + +function isBidRequestValid(bid) { + const publisherId = config.getConfig('apstream.publisherId'); + const isPublisherIdExist = !!(publisherId || bid.params.publisherId); + const isOneMediaType = Object.keys(bid.mediaTypes).length === 1; + + return isPublisherIdExist && isOneMediaType; +} + +function buildRequests(bidRequests, bidderRequest) { + const data = { + med: encodeURIComponent(window.location.href), + auid: bidderRequest.auctionId, + ref: document.referrer, + dnt: utils.getDNT() ? 1 : 0, + sr: getScreenParams() + }; + + const consentData = getRawConsentString(bidderRequest.gdprConsent); + data.iab_consent = consentData; + + const options = { + withCredentials: true + }; + + const isConsent = getIabConsentString(bidderRequest); + const noDsu = config.getConfig('apstream.noDsu'); + if (!isConsent || noDsu) { + data.dsu = ''; + } else { + data.dsu = dsuModule.readOrCreateDsu(); + } + + if (!isConsent || isConsent === 'disabled') { + options.withCredentials = false; + } + + const endpoints = getEndpointsGroups(bidRequests); + const serverRequests = endpoints.map(item => ({ + method: 'GET', + url: item.endpoint, + data: { + ...data, + bids: getBids(item.bids), + rnd: Math.random() + }, + options: options + })); + + return serverRequests; +} + +function interpretResponse(serverResponse) { + let bidResponses = serverResponse && serverResponse.body; + + if (!bidResponses || !bidResponses.length) { + return []; + } + + return bidResponses.map(x => ({ + requestId: x.bidId, + cpm: x.bidDetails.cpm, + width: x.bidDetails.width, + height: x.bidDetails.height, + creativeId: x.bidDetails.creativeId, + currency: x.bidDetails.currency || 'USD', + netRevenue: x.bidDetails.netRevenue, + dealId: x.bidDetails.dealId, + ad: injectPixels(x.bidDetails.ad, x.bidDetails.noticeUrls, x.bidDetails.impressionScripts), + ttl: x.bidDetails.ttl, + })); +} + +export const spec = { + code: CONSTANTS.BIDDER_CODE, + gvlid: CONSTANTS.GVLID, + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse +} + +registerBidder(spec); diff --git a/modules/apstreamBidAdapter.md b/modules/apstreamBidAdapter.md new file mode 100644 index 00000000000..6b87b33489a --- /dev/null +++ b/modules/apstreamBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: AP Stream Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@audienceproject.com +gdpr_supported: true +tcf2_supported: true +``` + +# Description + +Module that connects to AP Stream source + +# Inherit from prebid.js +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID // mandatory + } + }] + } + ]; +``` + +# Explicit ad-unit code +``` + var website = null; + switch (location.hostname) { + case "site1.com": + website = "S1"; + break; + case "site2.com": + website = "S2"; + break; + } + + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + code: website + '_Leaderboard' + } + }] + } + ]; +``` + +# Explicit ad-unit ID +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + adunitId: 1234 + } + }] + } + ]; +``` + +# DSU + +To disable DSU use config option: + +``` + pbjs.setConfig({ + apstream: { + noDsu: true + } + }); +``` + +To set `test` and `publisherId` parameters globally use config options (it can be overrided if set in specific bid): + +``` +pbjs.setBidderConfig({ + bidders: ["apstream"], + config: { + appstream: { + publisherId: '1234 + test: true + } + } +}); +``` diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js new file mode 100644 index 00000000000..8f06b8ed856 --- /dev/null +++ b/modules/asoBidAdapter.js @@ -0,0 +1,351 @@ +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 {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 = []; + + utils._each(validBidRequests, bidRequest => { + const payload = createBasePayload(bidRequest, bidderRequest); + + const bannerParams = utils.deepAccess(bidRequest, 'mediaTypes.banner'); + const videoParams = utils.deepAccess(bidRequest, 'mediaTypes.video'); + + let imp; + + if (bannerParams && videoParams) { + utils.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 (utils.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 = utils.tryAppendQueryString(query, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + query = utils.tryAppendQueryString(query, 'consents_str', gdprConsent.consentString); + const consentsIds = getConsentsIds(gdprConsent); + if (consentsIds) { + query = utils.tryAppendQueryString(query, 'consents', consentsIds); + } + } + + if (uspConsent) { + query = utils.tryAppendQueryString(query, 'us_privacy', uspConsent); + } + + utils._each(serverResponses, resp => { + const userSyncs = utils.deepAccess(resp, 'body.ext.user_syncs'); + if (!userSyncs) { + return; + } + + utils._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: utils.deepAccess(bid, 'renderer.options'), + adUnitCode: bid.adUnitCode + }); + renderer.setRender(outstreamRender); + return renderer; +} + +function getUrlsInfo(bidderRequest) { + let page = ''; + let referrer = ''; + + const {refererInfo} = bidderRequest; + + if (utils.inIframe()) { + page = refererInfo.referer; + } else { + const w = utils.getWindowTop(); + page = w.location.href; + referrer = w.document.referrer || ''; + } + + page = config.getConfig('pageUrl') || page; + const url = utils.parseUrl(page); + const domain = url.hostname; + + return { + domain, + page, + referrer + }; +} + +function getSize(paramSizes) { + const parsedSizes = utils.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 (!utils.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: utils.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 = utils.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: utils.getDNT() ? 1 : 0, + h: window.innerHeight, + w: window.innerWidth, + }, + imp: [], + ext: {}, + user: {} + }; + + if (bidRequest.params.attr) { + utils.deepSetValue(payload, 'site.ext.attr', bidRequest.params.attr); + } + + if (bidderRequest.gdprConsent) { + utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + const consentsIds = getConsentsIds(bidderRequest.gdprConsent); + if (consentsIds) { + utils.deepSetValue(payload, 'user.ext.consents', consentsIds); + } + utils.deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa')) { + utils.deepSetValue(payload, 'regs.coppa', 1); + } + + const eids = utils.deepAccess(bidRequest, 'userIdAsEids'); + if (eids && eids.length) { + utils.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 7e98c1022d2..2fec3892d27 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -31,7 +31,9 @@ function buildBid(bidData) { creativeId: bidData.content.seanceId, currency: bidData.currency, netRevenue: true, - mediaType: BANNER, + meta: { + mediaType: BANNER, + }, ttl: TTL, content: bidData.content }; @@ -62,7 +64,7 @@ function wrapAd(bid, bidData) { parentDocument.style.height = "100%"; parentDocument.style.width = "100%"; } - var _html = "${encodeURIComponent(JSON.stringify(bid))}"; + var _html = "${encodeURIComponent(JSON.stringify({...bid, content: bidData.content}))}"; window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); @@ -126,14 +128,9 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse) { - const serverBody = serverResponse.body; - if (serverBody && utils.isArray(serverBody)) { - return utils._map(serverBody, function(bid) { - return buildBid(bid); - }); - } else { - return []; - } + const bids = serverResponse.body && serverResponse.body.bids; + + return Array.isArray(bids) ? bids.map(bid => buildBid(bid)) : [] } } diff --git a/modules/astraoneBidAdapter.md b/modules/astraoneBidAdapter.md index a7eaeeef5a4..e090cfe1e54 100644 --- a/modules/astraoneBidAdapter.md +++ b/modules/astraoneBidAdapter.md @@ -18,17 +18,17 @@ About us: https://astraone.io var adUnits = [{ code: 'test-div', mediaTypes: { - banner: { - sizes: [1, 1] - } + banner: { + sizes: [1, 1], + } }, bids: [{ - bidder: "astraone", - params: { - placement: "inImage", - placeId: "5af45ad34d506ee7acad0c26", - imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" - } + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } }] }]; ``` @@ -39,66 +39,69 @@ var adUnits = [{ - - Prebid.js Banner Example - - + + }, + bids: [{ + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } + }] + }]; + + var pbjs = pbjs || {}; + pbjs.que = pbjs.que || []; + + pbjs.que.push(function() { + pbjs.addAdUnits(adUnits); + pbjs.requestBids({ + bidsBackHandler: function (e) { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + var params = pbjs.getAdserverTargetingForAdUnitCode("test-div"); + var iframe = document.getElementById('test-div'); + + if (params && params['hb_adid']) { + iframe.parentElement.style.position = "relative"; + iframe.style.display = "block"; + pbjs.renderAd(iframe.contentDocument, params['hb_adid']); + } + } + }); + }); + -

Prebid.js InImage Banner Test

+

Prebid.js InImage Banner Test

-
- - -
+
+ + +
@@ -109,90 +112,91 @@ var adUnits = [{ - - Prebid.js Banner Example - - - - + + Prebid.js Banner gpt Example + + + + -

Prebid.js Banner Ad Unit Test

+

Prebid.js InImage Banner gpt Test

-
- +
+ - -
+ +
``` 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 ad488aa50d9..0cff7bbd68f 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -3,26 +3,235 @@ 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'; + +export const storage = getStorageManager(); + +/** + * Analytics adapter for - https://liveramp.com + * Maintainer - prebid@liveramp.com + */ 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'; let handlerRequest = []; let handlerResponse = []; -let host = ''; + +let atsAnalyticsAdapterVersion = 1; + +let browsersList = [ + /* Googlebot */ + { + test: /googlebot/i, + name: 'Googlebot' + }, + + /* Opera < 13.0 */ + { + test: /opera/i, + name: 'Opera', + }, + + /* Opera > 13.0 */ + { + test: /opr\/|opios/i, + name: 'Opera' + }, + { + test: /SamsungBrowser/i, + name: 'Samsung Internet for Android', + }, + { + test: /Whale/i, + name: 'NAVER Whale Browser', + }, + { + test: /MZBrowser/i, + name: 'MZ Browser' + }, + { + test: /focus/i, + name: 'Focus', + }, + { + test: /swing/i, + name: 'Swing', + }, + { + test: /coast/i, + name: 'Opera Coast', + }, + { + test: /opt\/\d+(?:.?_?\d+)+/i, + name: 'Opera Touch', + }, + { + test: /yabrowser/i, + name: 'Yandex Browser', + }, + { + test: /ucbrowser/i, + name: 'UC Browser', + }, + { + test: /Maxthon|mxios/i, + name: 'Maxthon', + }, + { + test: /epiphany/i, + name: 'Epiphany', + }, + { + test: /puffin/i, + name: 'Puffin', + }, + { + test: /sleipnir/i, + name: 'Sleipnir', + }, + { + test: /k-meleon/i, + name: 'K-Meleon', + }, + { + test: /micromessenger/i, + name: 'WeChat', + }, + { + test: /qqbrowser/i, + name: (/qqbrowserlite/i).test(window.navigator.userAgent) ? 'QQ Browser Lite' : 'QQ Browser', + }, + { + test: /msie|trident/i, + name: 'Internet Explorer', + }, + { + test: /\sedg\//i, + name: 'Microsoft Edge', + }, + { + test: /edg([ea]|ios)/i, + name: 'Microsoft Edge', + }, + { + test: /vivaldi/i, + name: 'Vivaldi', + }, + { + test: /seamonkey/i, + name: 'SeaMonkey', + }, + { + test: /sailfish/i, + name: 'Sailfish', + }, + { + test: /silk/i, + name: 'Amazon Silk', + }, + { + test: /phantom/i, + name: 'PhantomJS', + }, + { + test: /slimerjs/i, + name: 'SlimerJS', + }, + { + test: /blackberry|\bbb\d+/i, + name: 'BlackBerry', + }, + { + test: /(web|hpw)[o0]s/i, + name: 'WebOS Browser', + }, + { + test: /bada/i, + name: 'Bada', + }, + { + test: /tizen/i, + name: 'Tizen', + }, + { + test: /qupzilla/i, + name: 'QupZilla', + }, + { + test: /firefox|iceweasel|fxios/i, + name: 'Firefox', + }, + { + test: /electron/i, + name: 'Electron', + }, + { + test: /MiuiBrowser/i, + name: 'Miui', + }, + { + test: /chromium/i, + name: 'Chromium', + }, + { + test: /chrome|crios|crmo/i, + name: 'Chrome', + }, + { + test: /GSA/i, + name: 'Google Search', + }, + + /* Android Browser */ + { + test: /android/i, + name: 'Android Browser', + }, + + /* PlayStation 4 */ + { + test: /playstation 4/i, + name: 'PlayStation 4', + }, + + /* Safari */ + { + test: /safari|applewebkit/i, + name: 'Safari', + }, +]; + +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) { + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); + let envelopeSource = envelopeSourceCookieValue === 'true'; let requests; requests = args.bids.map(function(bid) { return { + envelope_source: envelopeSource, has_envelope: bid.userId ? !!bid.userId.idl_env : false, bidder: bid.bidder, bid_id: bid.bidId, auction_id: args.auctionId, - user_browser: checkUserBrowser(), + user_browser: parseBrowser(), user_platform: navigator.platform, auction_start: new Date(args.auctionStart).toJSON(), domain: window.location.hostname, pid: atsAnalyticsAdapter.context.pid, + adapter_version: atsAnalyticsAdapterVersion }; }); return requests; @@ -38,58 +247,45 @@ function bidResponseHandler(args) { }; } -export function checkUserBrowser() { - let firefox = browserIsFirefox(); - let chrome = browserIsChrome(); - let edge = browserIsEdge(); - let safari = browserIsSafari(); - if (firefox) { - return firefox; - } if (chrome) { - return chrome; - } if (edge) { - return edge; - } if (safari) { - return safari; - } else { - return 'Unknown' - } -} - -export function browserIsFirefox() { - if (typeof InstallTrigger !== 'undefined') { - return 'Firefox'; - } else { - return false; - } -} - -export function browserIsIE() { - return !!document.documentMode; -} - -export function browserIsEdge() { - if (!browserIsIE() && !!window.StyleMedia) { - return 'Edge'; - } else { - return false; +export function parseBrowser() { + let ua = atsAnalyticsAdapter.getUserAgent(); + try { + let result = browsersList.filter(function(obj) { + return obj.test.test(ua); + }); + 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); } } -export function browserIsChrome() { - if ((!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) || (/Android/i.test(navigator.userAgent) && !!window.chrome)) { - return 'Chrome'; - } else { - return false; +function sendDataToAnalytic () { + // send data to ats analytic endpoint + try { + let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let strJSON = JSON.stringify(dataToSend); + utils.logInfo('ATS Analytics - tried to send analytics data!'); + ajax(analyticsUrl, function () { + }, strJSON, {method: 'POST', contentType: 'application/json'}); + } catch (err) { + utils.logError('ATS Analytics - request encounter an error: ', err); } } -export function browserIsSafari() { - if (window.safari !== undefined) { - return 'Safari' - } else { - return false; - } +// preflight request, to check did publisher have permission to send data to analytics endpoint +function preflightRequest (envelopeSourceCookieValue) { + utils.logInfo('ATS Analytics - preflight request!'); + 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) { @@ -117,7 +313,6 @@ function callHandler(evtype, args) { let atsAnalyticsAdapter = Object.assign(adapter( { - host, analyticsType }), { @@ -126,13 +321,18 @@ let atsAnalyticsAdapter = Object.assign(adapter( callHandler(eventType, args); } if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - // send data to ats analytic endpoint + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); try { - let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; - let strJSON = JSON.stringify(dataToSend); - ajax(atsAnalyticsAdapter.context.host, function () { - }, strJSON, {method: 'POST', contentType: 'application/json'}); + 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); } } } @@ -141,31 +341,40 @@ 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/100 +atsAnalyticsAdapter.shouldFireRequest = function (samplingRate) { + if (samplingRate !== 0) { + let shouldFireRequestValue = (Math.floor((Math.random() * 100 + 1)) === 100); + utils.logInfo('ATS Analytics - Should Fire Request: ', shouldFireRequestValue); + return shouldFireRequestValue; + } else { + utils.logInfo('ATS Analytics - Should Fire Request: ', false); + return false; + } +}; + +atsAnalyticsAdapter.getUserAgent = function () { + return window.navigator.userAgent; +}; // 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('Publisher ID (pid) option is not defined. Analytics won\'t work'); + utils.logError('ATS Analytics - Publisher ID (pid) option is not defined. Analytics won\'t work'); return; } - - if (!config.options.host) { - utils.logError('Host option is not defined. Analytics won\'t work'); - return; - } - - host = config.options.host; atsAnalyticsAdapter.context = { events: [], - host: config.options.host, pid: config.options.pid }; let initOptions = config.options; + utils.logInfo('ATS Analytics - adapter enabled! '); atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function }; adaptermanager.registerAnalyticsAdapter({ adapter: atsAnalyticsAdapter, - code: 'atsAnalytics' + code: 'atsAnalytics', + gvlid: 97 }); export default atsAnalyticsAdapter; diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md index 560ad237aa0..7c634f39ae2 100644 --- a/modules/atsAnalyticsAdapter.md +++ b/modules/atsAnalyticsAdapter.md @@ -17,7 +17,6 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp. provider: 'atsAnalytics', options: { pid: '999', // publisher ID - host: 'https://example.com' // host is provided to publisher } } ``` diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js deleted file mode 100644 index 816a6abd0e8..00000000000 --- a/modules/audienceNetworkBidAdapter.js +++ /dev/null @@ -1,308 +0,0 @@ -/** - * @file AudienceNetwork adapter. - */ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { generateUUID, deepAccess, convertTypes, formatQS } from '../src/utils.js'; -import findIndex from 'core-js-pure/features/array/find-index.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const code = 'audienceNetwork'; -const currency = 'USD'; -const method = 'GET'; -const url = 'https://an.facebook.com/v2/placementbid.json'; -const supportedMediaTypes = ['banner', 'video']; -const netRevenue = true; -const hbBidder = 'fan'; -const ttl = 600; -const videoTtl = 3600; -const platver = '$prebid.version$'; -const platform = '241394079772386'; -const adapterver = '1.3.0'; - -/** - * Does this bid request contain valid parameters? - * @param {Object} bid - * @returns {Boolean} - */ -const isBidRequestValid = bid => - typeof bid.params === 'object' && - typeof bid.params.placementId === 'string' && - bid.params.placementId.length > 0 && - Array.isArray(bid.sizes) && bid.sizes.length > 0 && - (isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).some(size => size === '300x250') : true) && - (isValidNonSizedFormat(bid.params.format) || bid.sizes.map(flattenSize).some(isValidSize)); - -/** - * Flattens a 2-element [W, H] array as a 'WxH' string, - * otherwise passes value through. - * @param {Array|String} size - * @returns {String} - */ -const flattenSize = size => - (Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size; - -/** - * Expands a 'WxH' string as a 2-element [W, H] array - * @param {String} size - * @returns {Array} - */ -const expandSize = size => size.split('x').map(Number); - -/** - * Is this a valid slot size? - * @param {String} size - * @returns {Boolean} - */ -const isValidSize = size => includes(['300x250', '320x50'], size); - -/** - * Is this a valid, non-sized format? - * @param {String} size - * @returns {Boolean} - */ -const isValidNonSizedFormat = format => includes(['video', 'native'], format); - -/** - * Is this a valid size and format? - * @param {String} size - * @returns {Boolean} - */ -const isValidSizeAndFormat = (size, format) => - (isFullWidth(format) && flattenSize(size) === '300x250') || - isValidNonSizedFormat(format) || - isValidSize(flattenSize(size)); - -/** - * Find a preferred entry, if any, from an array of valid sizes. - * @param {Array} acc - * @param {String} cur - */ -const sortByPreferredSize = (acc, cur) => - (cur === '300x250') ? [cur, ...acc] : [...acc, cur]; - -/** - * Map any deprecated size/formats to new values. - * @param {String} size - * @param {String} format - */ -const mapDeprecatedSizeAndFormat = (size, format) => - isFullWidth(format) ? ['300x250', null] : [size, format]; - -/** - * Is this a video format? - * @param {String} format - * @returns {Boolean} - */ -const isVideo = format => format === 'video'; - -/** - * Is this a fullwidth format? - * @param {String} format - * @returns {Boolean} - */ -const isFullWidth = format => format === 'fullwidth'; - -/** - * Which SDK version should be used for this format? - * @param {String} format - * @returns {String} - */ -const sdkVersion = format => isVideo(format) ? '' : '6.0.web'; - -/** - * Which platform identifier should be used? - * @param {Array} platforms Possible platform identifiers - * @returns {String} First valid platform found, or default if none found - */ -const findPlatform = platforms => [...platforms.filter(Boolean), platform][0]; - -/** - * Does the search part of the URL contain "anhb_testmode" - * and therefore indicate testmode should be used? - * @returns {String} "true" or "false" - */ -const isTestmode = () => Boolean( - window && window.location && - typeof window.location.search === 'string' && - window.location.search.indexOf('anhb_testmode') !== -1 -).toString(); - -/** - * Generate ad HTML for injection into an iframe - * @param {String} placementId - * @param {String} format - * @param {String} bidId - * @returns {String} HTML - */ -const createAdHtml = (placementId, format, bidId) => { - const nativeStyle = format === 'native' ? '' : ''; - const nativeContainer = format === 'native' ? '
' : ''; - return ` - ${nativeStyle} - -
- - - ${nativeContainer} -
- -`; -}; - -/** - * Convert each bid request to a single URL to fetch those bids. - * @param {Array} bids - list of bids - * @param {String} bids[].placementCode - Prebid placement identifier - * @param {Object} bids[].params - * @param {String} bids[].params.placementId - Audience Network placement identifier - * @param {String} bids[].params.platform - Audience Network platform identifier (optional) - * @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set - * @param {Array} bids[].sizes - list of desired advert sizes - * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50] - * @returns {Array} List of URLs to fetch, plus formats and sizes for later use with interpretResponse - */ -const buildRequests = (bids, bidderRequest) => { - // Build lists of placementids, adformats, sizes and SDK versions - const placementids = []; - const adformats = []; - const sizes = []; - const sdk = []; - const platforms = []; - const requestIds = []; - - bids.forEach(bid => bid.sizes - .map(flattenSize) - .filter(size => isValidSizeAndFormat(size, bid.params.format)) - .reduce(sortByPreferredSize, []) - .slice(0, 1) - .forEach(preferredSize => { - const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format); - placementids.push(bid.params.placementId); - adformats.push(format || size); - sizes.push(size); - sdk.push(sdkVersion(format)); - platforms.push(bid.params.platform); - requestIds.push(bid.bidId); - }) - ); - // Build URL - const testmode = isTestmode(); - const pageurl = encodeURIComponent(deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(bidderRequest, 'refererInfo.referer')); - const platform = findPlatform(platforms); - const cb = generateUUID(); - const search = { - placementids, - adformats, - testmode, - pageurl, - sdk, - adapterver, - platform, - platver, - cb - }; - const video = findIndex(adformats, isVideo); - if (video !== -1) { - [search.playerwidth, search.playerheight] = expandSize(sizes[video]); - } - const data = formatQS(search); - - return [{ adformats, data, method, requestIds, sizes, url, pageurl }]; -}; - -/** - * Convert a server response to a bid response. - * @param {Object} response - object representing the response - * @param {Object} response.body - response body, already converted from JSON - * @param {Object} bidRequests - the original bid requests - * @param {Array} bidRequest.adformats - list of formats for the original bid requests - * @param {Array} bidRequest.sizes - list of sizes fot the original bid requests - * @returns {Array} A list of bid response objects - */ -const interpretResponse = ({ body }, { adformats, requestIds, sizes, pageurl }) => { - const { bids = {} } = body; - return Object.keys(bids) - // extract Array of bid responses - .map(placementId => bids[placementId]) - // flatten - .reduce((a, b) => a.concat(b), []) - // transform to bidResponse - .map((bid, i) => { - const { - bid_id: fbBidid, - placement_id: creativeId, - bid_price_cents: cpm - } = bid; - - const format = adformats[i]; - const [width, height] = expandSize(flattenSize(sizes[i])); - const ad = createAdHtml(creativeId, format, fbBidid); - const requestId = requestIds[i]; - - const bidResponse = { - // Prebid attributes - requestId, - cpm: cpm / 100, - width, - height, - ad, - ttl, - creativeId, - netRevenue, - currency, - // Audience Network attributes - hb_bidder: hbBidder, - fb_bidid: fbBidid, - fb_format: format, - fb_placementid: creativeId - }; - // Video attributes - if (isVideo(format)) { - bidResponse.mediaType = 'video'; - bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fbBidid}`; - bidResponse.ttl = videoTtl; - } - return bidResponse; - }); -}; - -/** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ -const transformBidParams = (params, isOpenRtb) => { - return convertTypes({ - 'placementId': 'string' - }, params); -} - -export const spec = { - code, - supportedMediaTypes, - isBidRequestValid, - buildRequests, - interpretResponse, - transformBidParams -}; - -registerBidder(spec); diff --git a/modules/audienceNetworkBidAdapter.md b/modules/audienceNetworkBidAdapter.md deleted file mode 100644 index 6147191f4b7..00000000000 --- a/modules/audienceNetworkBidAdapter.md +++ /dev/null @@ -1,49 +0,0 @@ -# Overview - -Module Name: Audience Network Bid Adapter - -Module Type: Bidder Adapter - -Maintainer: Lovell Fuller - -# Parameters - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | -| `placementId` | required | The Placement ID from Audience Network | "555555555555555\_555555555555555" | -| `format` | optional | Format, one of "native" or "video" | "native" | - -# Example ad units - -```javascript -const adUnits = [{ - code: "test-iab", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-native", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "native", - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-video", - sizes: [[640, 360]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "video", - placementId: "555555555555555_555555555555555" - } - }] -}]; -``` diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index b90471ee21a..da0cbb39925 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -4,10 +4,52 @@ 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') || + utils.deepAccess(bidderRequest, 'refererInfo.referer') || + null + ); +} + +/** + * Returns bidfloor through floors module if available + * + * @param {Object} bid + * @returns {number} + */ +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.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], @@ -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 => { + buildRequests: function (bidRequests, bidderRequest) { + const bids = bidRequests.map((bid) => { const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes', []); return { zoneId: utils.getValue(bid.params, 'zoneId'), - sizes: sizes.map(size => ({ + 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) + transactionId: utils.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, + }, }; }, @@ -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 (!utils.isArray(timeoutData)) { + return; + } + + timeoutData.forEach((bid) => { + const bidOnTimeout = requestedBids.find((requestedBid) => requestedBid.bidId === bid.bidId); + + if (bidOnTimeout) { + utils.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/audigentRtdProvider.js b/modules/audigentRtdProvider.js deleted file mode 100644 index 0f32c84962f..00000000000 --- a/modules/audigentRtdProvider.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * This module adds audigent provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will fetch segments from audigent server - * @module modules/audigentRtdProvider - * @requires module:modules/realTimeData - */ - -/** - * @typedef {Object} ModuleParams - * @property {string} siteKey - * @property {string} pubKey - * @property {string} url - * @property {?string} keyName - * @property {number} auctionDelay - */ - -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import * as utils from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -/** @type {string} */ -const MODULE_NAME = 'realTimeData'; - -/** @type {ModuleParams} */ -let _moduleParams = {}; - -/** - * XMLHttpRequest to get data form audigent server - * @param {string} url server url with query params - */ - -export function setData(data) { - storage.setDataInLocalStorage('__adgntseg', JSON.stringify(data)); -} - -function getSegments(adUnits, onDone) { - try { - let jsonData = storage.getDataFromLocalStorage('__adgntseg'); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.audigent_segments) { - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - rp[adUnitCode] = data; - return rp; - }, {}); - - onDone(dataToReturn); - return; - } - } - getSegmentsAsync(adUnits, onDone); - } catch (e) { - getSegmentsAsync(adUnits, onDone); - } -} - -function getSegmentsAsync(adUnits, onDone) { - const userIds = (getGlobal()).getUserIds(); - let tdid = null; - - if (userIds && userIds['tdid']) { - tdid = userIds['tdid']; - } else { - onDone({}); - } - - const url = `https://seg.ad.gt/api/v1/rtb_segments?tdid=${tdid}`; - - ajax(url, { - success: function (response, req) { - if (req.status === 200) { - try { - const data = JSON.parse(response); - if (data && data.audigent_segments) { - setData(data); - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - rp[adUnitCode] = data; - return rp; - }, {}); - - onDone(dataToReturn); - } else { - onDone({}); - } - } catch (err) { - utils.logError('unable to parse audigent segment data'); - onDone({}) - } - } else if (req.status === 204) { - // unrecognized site key - onDone({}); - } - }, - error: function () { - onDone({}); - utils.logError('unable to get audigent segment data'); - } - } - ); -} - -/** @type {RtdSubmodule} */ -export const audigentSubmodule = { - /** - * used to link submodule with realTimeData - * @type {string} - */ - name: 'audigent', - /** - * get data and send back to realTimeData module - * @function - * @param {adUnit[]} adUnits - * @param {function} onDone - */ - getData: getSegments -}; - -export function init(config) { - const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { - try { - _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === 'audigent')[0].params; - _moduleParams.auctionDelay = realTimeData.auctionDelay; - } catch (e) { - _moduleParams = {}; - } - confListener(); - }); -} - -submodule('realTimeData', audigentSubmodule); -init(config); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md deleted file mode 100644 index 47bcbbbf951..00000000000 --- a/modules/audigentRtdProvider.md +++ /dev/null @@ -1,52 +0,0 @@ -Audigent is a next-generation data management platform and a first-of-a-kind -"data agency" containing some of the most exclusive content-consuming audiences -across desktop, mobile and social platforms. - -This real-time data module provides first-party Audigent segments that can be -attached to bid request objects destined for different SSPs in order to optimize -targeting. Audigent maintains a large database of first-party Tradedesk Unified -ID to third party segment mappings that can now be queried at bid-time. - -Usage: - -Compile the audigent RTD module into your Prebid build: - -`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -Audigent segments will then be attached to each bid request objects in -`bid.realTimeData.audigent_segments` - -The format of the segments is a per-SSP mapping: - -``` -{ - 'appnexus': ['anseg1', 'anseg2'], - 'google': ['gseg1', 'gseg2'] -} -``` - -If a given SSP's API backend supports segment fields, they can then be -attached prior to the bid request being sent: - -``` -pbjs.requestBids({bidsBackHandler: addAudigentSegments}); - -function addAudigentSegments() { - for (i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - for (j = 0; j < adUnit.bids.length; j++) { - adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].realTimeData.audigent_segments['rubicon']; - } - } -} -``` - -To view an example of the segments returned by Audigent's backends: - -`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -and then point your browser at: - -`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` - - diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 95d225cb5f7..415c52ba6d3 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -27,17 +27,20 @@ export const spec = { } const siteId = validBidRequests[0].params.siteId - const placementId = validBidRequests[0].params.placementId - const impressions = validBidRequests.map(bidRequest => ({ - id: bidRequest.bidId, - banner: { - format: bidRequest.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1], - })) - }, - })) + const impressions = validBidRequests.map(bidRequest => { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + placement: bidRequest.params.placementId, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } + }) // params from bid request const openrtbRequest = { @@ -45,7 +48,6 @@ export const spec = { imp: impressions, site: { id: siteId, - placement: placementId, domain: window.location.hostname, page: window.location.href, ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, @@ -69,20 +71,24 @@ export const spec = { const bidResponses = [] const response = (serverResponse || {}).body - 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, - ad: bid.adm, - adDomain: bid.adomain[0], - currency: DEFAULT_CURRENCY, - ttl: DEFAULT_BID_TTL, - creativeId: bid.crid, - width: bid.w, - height: bid.h, - netRevenue: DEFAULT_NET_REVENUE, - nurl: bid.nurl, + if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid.forEach(bidObj => { + bidObj.bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain + }, + currency: DEFAULT_CURRENCY, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + width: bid.w, + height: bid.h, + netRevenue: DEFAULT_NET_REVENUE, + nurl: bid.nurl, + }) }) }) } else { diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js deleted file mode 100644 index 1163ac830ba..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.getConfig('fpd'); - - // 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) { - id5id = bidRequests[0].userId.id5id; - } - - // 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 new file mode 100644 index 00000000000..daaac27e6a4 --- /dev/null +++ b/modules/axonixBidAdapter.js @@ -0,0 +1,185 @@ +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'; +const BIDDER_VERSION = '1.0.2'; + +const CURRENCY = 'USD'; +const DEFAULT_REGION = 'us-east-1'; + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: CURRENCY, + mediaType: '*', + size: '*' + }); + } + + return floorInfo.floor || 0; +} + +function getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +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 getURL(params, path) { + let { supplyId, region, endpoint } = params; + let url; + + if (endpoint) { + url = endpoint; + } else if (region) { + url = `https://openrtb-${region}.axonix.com/supply/${path}/${supplyId}`; + } else { + url = `https://openrtb-${DEFAULT_REGION}.axonix.com/supply/${path}/${supplyId}` + } + + return url; +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + // video bid request validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('mimes') || + !utils.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)); + + return false; + } + } + + return !!(bid.params && bid.params.supplyId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + // device.connectiontype + let connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection) + let connectionType = 'unknown'; + let effectiveType = ''; + + if (connection) { + if (connection.type) { + connectionType = connection.type; + } + + if (connection.effectiveType) { + effectiveType = connection.effectiveType; + } + } + + const requests = validBidRequests.map(validBidRequest => { + // app/site + let app; + let site; + + if (typeof config.getConfig('app') === 'object') { + app = config.getConfig('app'); + } else { + site = { + page: getPageUrl(validBidRequest, bidderRequest) + } + } + + const data = { + app, + site, + validBidRequest, + connectionType, + effectiveType, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, + bidfloor: getBidFloor(validBidRequest), + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + language: navigator.language, + prebidVersion: '$prebid.version$', + screenHeight: screen.height, + screenWidth: screen.width, + tmax: config.getConfig('bidderTimeout'), + ua: navigator.userAgent, + }; + + return { + method: 'POST', + url: getURL(validBidRequest.params, 'prebid'), + options: { + withCredentials: false, + contentType: 'application/json' + }, + data + }; + }); + + return requests; + }, + + interpretResponse: function(serverResponse) { + const response = serverResponse ? serverResponse.body : []; + + if (!utils.isArray(response)) { + return []; + } + + const responses = []; + + for (const resp of response) { + if (resp.requestId) { + responses.push(Object.assign(resp, { + ttl: config.getConfig('_bidderTimeout') + })); + } + } + + return responses; + }, + + onTimeout: function(timeoutData) { + const params = utils.deepAccess(timeoutData, '0.params.0'); + + if (!utils.isEmpty(params)) { + ajax(getURL(params, 'prebid/timeout'), null, timeoutData[0], { + method: 'POST', + options: { + withCredentials: false, + contentType: 'application/json' + } + }); + } + }, + + onBidWon: function(bid) { + const { nurl } = bid || {}; + + if (bid.nurl) { + utils.triggerPixel(utils.replaceAuctionPrice(nurl, bid.cpm)); + }; + } +} + +registerBidder(spec); diff --git a/modules/axonixBidAdapter.md b/modules/axonixBidAdapter.md new file mode 100644 index 00000000000..acbaae1d4b0 --- /dev/null +++ b/modules/axonixBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name : Axonix Bidder Adapter +Module Type : Bidder Adapter +Maintainer : support.axonix@emodoinc.com +``` + +# Description + +Module that connects to Axonix's exchange for bids. + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :------------------------------------- | +| `supplyId` | required | Supply UUID | `"2c426f78-bb18-4a16-abf4-62c6cd0ee8de"` | +| `region` | optional | Cloud region | `"us-east-1"` | +| `endpoint` | optional | Supply custom endpoint | `"https://open-rtb.axonix.com/custom"` | +| `instl` | optional | Set to 1 if using interstitial (default: 0) | `1` | + +# Test Parameters + +## Banner + +```javascript +var bannerAdUnit = { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Video + +```javascript +var videoAdUnit = { + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Native + +```javascript +var nativeAdUnit = { + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Multiformat + +```javascript +var adUnits = [ +{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +} +]; +``` diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 12e78c684ad..3531fa45d1b 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -6,17 +6,25 @@ 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.11'; +const ADAPTER_VERSION = '1.18'; 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'; export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; 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: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' }, + { key: 'haloId', source: 'audigent.com', atype: 1, queryParam: 'haloid' } +]; + let appId = ''; export const spec = { @@ -24,7 +32,27 @@ export const spec = { supportedMediaTypes: [ VIDEO, BANNER ], isBidRequestValid(bid) { - return !!(isVideoBidValid(bid) || isBannerBidValid(bid)); + if (isVideoBid(bid)) { + if (!getVideoBidParam(bid, 'appId')) { + utils.logWarn('Beachfront: appId param is required for video bids.'); + return false; + } + if (!getVideoBidParam(bid, 'bidfloor')) { + utils.logWarn('Beachfront: bidfloor param is required for video bids.'); + return false; + } + } + if (isBannerBid(bid)) { + if (!getBannerBidParam(bid, 'appId')) { + utils.logWarn('Beachfront: appId param is required for banner bids.'); + return false; + } + if (!getBannerBidParam(bid, 'bidfloor')) { + utils.logWarn('Beachfront: bidfloor param is required for banner bids.'); + return false; + } + } + return true; }, buildRequests(bids, bidderRequest) { @@ -56,28 +84,39 @@ export const spec = { response = response.body; if (isVideoBid(bidRequest)) { - if (!response || !response.url || !response.bidPrice) { + if (!response || !response.bidPrice) { utils.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'); - return { + let responseType = getVideoBidParam(bidRequest, 'responseType') || 'both'; + let responseMeta = Object.assign({ mediaType: VIDEO, advertiserDomains: [] }, response.meta); + let bidResponse = { requestId: bidRequest.bidId, bidderCode: spec.code, - vastUrl: response.url, - vastXml: response.vast, cpm: response.bidPrice, 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 }; + + if (responseType === 'nurl' || responseType === 'both') { + bidResponse.vastUrl = response.url; + } + + if (responseType === 'adm' || responseType === 'both') { + bidResponse.vastXml = response.vast; + } + + return bidResponse; } else { if (!response || !response.length) { utils.logWarn(`No valid banner bids from ${spec.code} bidder`); @@ -87,6 +126,7 @@ export const spec = { .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, @@ -95,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 }; @@ -236,6 +277,16 @@ function getPlayerBidParam(bid, key, defaultValue) { return param === undefined ? defaultValue : param; } +function getBannerBidFloor(bid) { + let floorInfo = utils.isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = utils.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'); } @@ -257,13 +308,47 @@ function getTopWindowReferrer() { } } +function getEids(bid) { + return SUPPORTED_USER_IDS + .map(getUserId(bid)) + .filter(x => x); +} + +function getUserId(bid) { + return ({ key, source, rtiPartner, atype }) => { + let id = utils.deepAccess(bid, `userId.${key}`); + return id ? formatEid(id, source, rtiPartner, atype) : null; + }; +} + +function formatEid(id, source, rtiPartner, atype) { + let uid = { id }; + if (rtiPartner) { + uid.ext = { rtiPartner }; + } + if (atype) { + uid.atype = atype; + } + return { + source, + uids: [uid] + }; +} + 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) { @@ -271,9 +356,10 @@ 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 payload = { isPrebid: true, appId: appId, @@ -306,10 +392,13 @@ function createVideoRequestData(bid, bidderRequest) { regs: { ext: {} }, + source: { + ext: {} + }, user: { ext: {} }, - cur: ['USD'] + cur: [CURRENCY] }; if (bidderRequest && bidderRequest.uspConsent) { @@ -322,16 +411,12 @@ function createVideoRequestData(bid, bidderRequest) { payload.user.ext.consent = consentString; } - if (bid.userId && bid.userId.tdid) { - payload.user.ext.eids = [{ - source: 'adserver.org', - uids: [{ - id: bid.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }]; + if (bid.schain) { + payload.source.ext.schain = bid.schain; + } + + if (eids.length > 0) { + payload.user.ext.eids = eids; } let connection = navigator.connection || navigator.webkitConnection; @@ -349,7 +434,8 @@ 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) }; }); @@ -378,10 +464,17 @@ function createBannerRequestData(bids, bidderRequest) { payload.gdprConsent = consentString; } - if (bids[0] && bids[0].userId && bids[0].userId.tdid) { - payload.tdid = bids[0].userId.tdid; + if (bids[0] && bids[0].schain) { + payload.schain = bids[0].schain; } + SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { + let id = utils.deepAccess(bids, `0.userId.${key}`) + if (id) { + payload[queryParam] = id; + } + }); + return payload; } diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md index 0a6b8b73da4..9de415f8fc5 100644 --- a/modules/beachfrontBidAdapter.md +++ b/modules/beachfrontBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Beachfront Bid Adapter Module Type: Bidder Adapter -Maintainer: john@beachfront.com +Maintainer: prebid@beachfront.com # Description @@ -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..aed5d04a151 --- /dev/null +++ b/modules/beopBidAdapter.js @@ -0,0 +1,142 @@ +import * as utils 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 = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + 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: utils.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 && utils.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); + + utils.logWarn(BIDDER_CODE + ': timed out request'); + utils.triggerPixel(utils.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); + + utils.logInfo(BIDDER_CODE + ': won request'); + utils.triggerPixel(utils.buildUrl({ + protocol: 'https', + hostname: 't.beop.io', + pathname: '/bid', + search: trackingParams + })); + }, + onSetTargeting: function(bid) {} +} + +function buildTrackingParams(data, info, value) { + return { + pid: data.params.accountId, + nid: data.params.networkId, + nptnid: data.params.networkPartnerId, + bid: data.bidId, + sl_n: data.adUnitCode, + aid: data.auctionId, + se_ca: 'bid', + se_ac: info, + se_va: value + }; +} + +function beOpRequestSlotsMaker(bid) { + const bannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + const publisherCurrency = config.getConfig('currency.adServerCurrency') || utils.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: utils.isArray(bannerSizes) ? bannerSizes : bid.sizes, + flr: floor, + pid: utils.getValue(bid.params, 'accountId'), + nid: utils.getValue(bid.params, 'networkId'), + nptnid: utils.getValue(bid.params, 'networkPartnerId'), + bid: utils.getBidIdParameter('bidId', bid), + brid: utils.getBidIdParameter('bidderRequestId', bid), + name: utils.getBidIdParameter('adUnitCode', bid), + aid: utils.getBidIdParameter('auctionId', bid), + tid: utils.getBidIdParameter('transactionId', bid), + brc: utils.getBidIdParameter('bidRequestsCount', bid), + bdrc: utils.getBidIdParameter('bidderRequestCount', bid), + bwc: utils.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 c435a5a993e..7ac1e4edf15 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,6 +1,9 @@ import {registerBidder} from '../src/adapters/bidderFactory.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'; export const spec = { code: BIDDER_CODE, @@ -18,16 +21,18 @@ 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) { let requests = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const refInfo = getRefererInfo(); - validBidRequests.forEach(i => { + validBidRequests.forEach((i) => { let params = { - sizes: parseSizesInput(getAdUnitSizes(i)).join('%2C'), + eids: getUsersIds(i), + sizes: parseSizesInput(getAdUnitSizes(i)), jst: 'hb', ord: Math.random() * 10000000000000000, tz: getTz(), @@ -38,6 +43,7 @@ export const spec = { transactionid: i.transactionId, auctionid: i.auctionId }; + if (i.params.itu !== undefined) { params.itu = i.params.itu; } @@ -56,6 +62,12 @@ export const spec = { } } + if (i.schain) { + params.schain = encodeToBase64WebSafe(JSON.stringify(i.schain)); + } + + if (refInfo && refInfo.referer) params.ref = refInfo.referer; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies !== 'undefined') { params.gdprApplies = !!gdprConsent.gdprApplies; @@ -65,9 +77,14 @@ export const spec = { } } - requests.push({method: 'GET', url: 'https://ads.betweendigital.com/adjson', data: params}) + requests.push({data: params}) }) - return requests; + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requests) + } + // return requests; }, /** * Unpack the response from the server into a list of bids. @@ -87,7 +104,10 @@ export const spec = { 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); } @@ -129,6 +149,10 @@ export const spec = { } } +function getUsersIds({ userIdAsEids }) { + return (userIdAsEids && userIdAsEids.length !== 0) ? userIdAsEids : []; +} + function getRr() { try { var td = top.document; @@ -161,6 +185,10 @@ function getTz() { return new Date().getTimezoneOffset(); } +function encodeToBase64WebSafe(string) { + return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + /* function get_pubdata(adds) { if (adds !== undefined && adds.pubdata !== undefined) { diff --git a/modules/bidViewability.js b/modules/bidViewability.js new file mode 100644 index 00000000000..c3b72cda8d4 --- /dev/null +++ b/modules/bidViewability.js @@ -0,0 +1,97 @@ +// This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Bidders and Analytics adapters +// GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent +// Does not work with other than GPT integration + +import { config } from '../src/config.js'; +import * as events from '../src/events.js'; +import { EVENTS } 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'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'bidViewability'; +const CONFIG_ENABLED = 'enabled'; +const CONFIG_FIRE_PIXELS = 'firePixels'; +const CONFIG_CUSTOM_MATCH = 'customMatchFunction'; +const BID_VURL_ARRAY = 'vurls'; +const GPT_IMPRESSION_VIEWABLE_EVENT = 'impressionViewable'; + +export let isBidAdUnitCodeMatchingSlot = (bid, slot) => { + return (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode); +} + +export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { + return find(getGlobal().getAllWinningBids(), + // supports custom match function from config + bid => isFn(globalModuleConfig[CONFIG_CUSTOM_MATCH]) + ? globalModuleConfig[CONFIG_CUSTOM_MATCH](bid, slot) + : isBidAdUnitCodeMatchingSlot(bid, slot) + ) || null; +}; + +export let fireViewabilityPixels = (globalModuleConfig, bid) => { + if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) { + let queryParams = {}; + + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + + bid[BID_VURL_ARRAY].forEach(url => { + // add '?' if not present in URL + if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { + url += '?'; + } + // append all query params, `&key=urlEncoded(value)` + url += Object.keys(queryParams).reduce((prev, key) => prev += `&${key}=${encodeURIComponent(queryParams[key])}`, ''); + triggerPixel(url) + }); + } +}; + +export let logWinningBidNotFound = (slot) => { + logWarn(`bid details could not be found for ${slot.getSlotElementId()}, probable reasons: a non-prebid bid is served OR check the prebid.AdUnit.code to GPT.AdSlot relation.`); +}; + +export let impressionViewableHandler = (globalModuleConfig, slot, event) => { + let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); + if (respectiveBid === null) { + logWinningBidNotFound(slot); + } else { + // if config is enabled AND VURL array is present then execute each pixel + fireViewabilityPixels(globalModuleConfig, respectiveBid); + // 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); + } +}; + +export let init = () => { + events.on(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 + // this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled + if (globalModuleConfig[CONFIG_ENABLED] !== true) { + return; + } + // add the GPT event listener + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().addEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, function(event) { + impressionViewableHandler(globalModuleConfig, event.slot, event); + }); + }); + }); +} + +init() diff --git a/modules/bidViewability.md b/modules/bidViewability.md new file mode 100644 index 00000000000..78a1539fb1a --- /dev/null +++ b/modules/bidViewability.md @@ -0,0 +1,49 @@ +# Overview + +Module Name: bidViewability + +Purpose: Track when a bid is viewable + +Maintainer: harshad.mane@pubmatic.com + +# Description +- This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Analytics adapters, bidders will need to implement `onBidViewable` method to capture this event +- Bidderes can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewability')``` +- GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent . This event is fired when an impression becomes viewable, according to the Active View criteria. +Refer: https://support.google.com/admanager/answer/4524488 +- The module does not work with adserver other than GAM with GPT integration +- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using param ```customMatchFunction``` +- When a rendered PBJS bid is viewable the module will trigger BID_VIEWABLE event, which can be consumed by bidders and analytics adapters +- For the viewable bid if ```bid.vurls type array``` param is and module config ``` firePixels: true ``` is set then the URLs mentioned in bid.vurls will be executed. Please note that GDPR and USP related parameters will be added to the given URLs + +# Params +- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable +- firePixels [optional] [type: boolean], when set to true, will fire the urls mentioned in bid.vurls which should be array of urls +- customMatchFunction [optional] [type: function(bid, slot)], when passed this function will be used to `find` the matching winning bid for the GPT slot. Default value is ` (bid, slot) => (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ` + +# 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({ + bidViewability: { + enabled: true, + firePixels: true, + customMatchFunction: function(bid, slot){ + console.log('using custom match function....'); + return bid.adUnitCode === slot.getAdUnitPath(); + } + } + }); +``` + +# Please Note: +- Doesn't seems to work with Instream Video, https://docs.prebid.org/dev-docs/examples/instream-banner-mix.html as GPT's impressionViewable event is not triggered for instream-video-creative +- Works with Banner, Outsteam, Native creatives + diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js new file mode 100644 index 00000000000..4651e424d00 --- /dev/null +++ b/modules/bidViewabilityIO.js @@ -0,0 +1,91 @@ +import { config } from '../src/config.js'; +import * as events from '../src/events.js'; +import { EVENTS } from '../src/constants.json'; +import * as utils from '../src/utils.js'; + +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 utils.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(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(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 6db35f184ca..b77ca474e13 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; }; let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; + var ori = [window.location.protocol + '//' + window.location.hostname]; if (window.location.ancestorOrigins) { for (var i = 0; i < window.location.ancestorOrigins.length; i++) { @@ -56,7 +56,7 @@ export const spec = { // Derive the parent origin var parts = document.referrer.split('/'); - ori.push('https://' + parts[2]); + ori.push(parts[0] + '//' + parts[2]); if (window.parent !== window.top) { // Additional unknown origins exist @@ -67,15 +67,30 @@ export const spec = { return ori; }; + 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)); - // Stuff to send: [bid id, sizes, adUnitId] + var adUnitId = utils.getBidIdParameter('adUnitId', bid.params); + var options = utils.deepClone(bid.params); + + delete options.adUnitId; + + // Merge externally set targeting params + if (typeof bidglass === 'object' && bidglass.getTargeting) { + let targeting = bidglass.getTargeting(adUnitId, options.targeting); + + if (targeting && Object.keys(targeting).length > 0) options.targeting = targeting; + } + + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + adUnitId: adUnitId, + options: options }); }); @@ -110,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', + utils._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/bidscubeBidAdapter.js b/modules/bidscubeBidAdapter.js new file mode 100644 index 00000000000..d3f27a5ac6d --- /dev/null +++ b/modules/bidscubeBidAdapter.js @@ -0,0 +1,90 @@ +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 = '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(parseInt(opts.params.placementId))) + }, + + buildRequests: function (validBidRequests) { + validBidRequests = validBidRequests || [] + let winTop = window + try { + window.top.location.toString() + winTop = window.top + } catch (e) { utils.logMessage(e) } + + const location = utils.getWindowLocation() + const placements = [] + + for (let i = 0; i < validBidRequests.length; i++) { + const p = validBidRequests[i] + + placements.push({ + placementId: p.params.placementId, + bidId: p.bidId, + traffic: p.params.traffic || BANNER, + allParams: JSON.stringify(p) + }) + } + + return { + method: 'POST', + url: URL, + data: { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: +(location.protocol === 'https:'), + host: location.hostname, + page: location.pathname, + placements: placements + } + } + }, + + interpretResponse: function (opts) { + const body = opts.body + const response = [] + + for (let i = 0; i < body.length; i++) { + const item = body[i] + if (isBidResponseValid(item)) { + response.push(item) + } + } + + return response + }, + + getUserSyncs: function (syncOptions, serverResponses) { + return [{ type: 'image', url: URL_SYNC }] + } +} + +registerBidder(spec) + +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.title && bid.image && bid.impressionTrackers) + default: + return false + } +} 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/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js new file mode 100644 index 00000000000..95c53b78de2 --- /dev/null +++ b/modules/bizzclickBidAdapter.js @@ -0,0 +1,322 @@ +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}`; +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' + } +}; +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. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + }, + /** + * 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 && validBidRequests.length === 0) return [] + let accuontId = validBidRequests[0].params.accountId; + const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let bids = []; + for (let bidRequest of validBidRequests) { + let impObject = prepareImpObject(bidRequest); + let data = { + id: bidRequest.bidId, + test: config.getConfig('debug') ? 1 : 0, + at: 1, + cur: ['USD'], + device: { + w: winTop.screen.width, + h: winTop.screen.height, + dnt: utils.getDNT() ? 1 : 0, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + }, + site: { + page: location.pathname, + host: location.host + }, + source: { + tid: bidRequest.transactionId + }, + 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) { + 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); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + } + bids.push(data) + } + return { + method: 'POST', + url: endpointURL, + data: bids + }; + }, + /** + * 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 || !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, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.ttl || 1200, + currency: response.cur || 'USD', + netRevenue: true, + creativeId: response.seatbid[0].bid[0].crid, + dealId: response.seatbid[0].bid[0].dealid, + 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 + bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl + break + case NATIVE: + bid.native = parseNative(response.seatbid[0].bid[0].adm) + break + default: + bid.ad = response.seatbid[0].bid[0].adm + } + bids.push(bid); + } + return bids; + }, +}; +/** + * Determine type of request + * + * @param bidRequest + * @param type + * @returns {boolean} + */ +const checkRequestType = (bidRequest, type) => { + return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); +} +const parseNative = admObject => { + const { assets, link, imptrackers, jstracker } = admObject.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : 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 }; + } + }); + return result; +} +const prepareImpObject = (bidRequest) => { + let impObject = { + id: bidRequest.transactionId, + secure: 1, + ext: { + placementId: bidRequest.params.placementId + } + }; + if (checkRequestType(bidRequest, BANNER)) { + impObject.banner = addBannerParameters(bidRequest); + } + if (checkRequestType(bidRequest, VIDEO)) { + impObject.video = addVideoParameters(bidRequest); + } + if (checkRequestType(bidRequest, NATIVE)) { + impObject.native = { + ver: NATIVE_VERSION, + request: addNativeParameters(bidRequest) + }; + } + return impObject +}; +const addNativeParameters = bidRequest => { + let impObject = { + id: bidRequest.transactionId, + ver: NATIVE_VERSION, + }; + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + 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'); + bannerObject.w = size[0]; + bannerObject.h = size[1]; + return bannerObject; +}; +const parseSizes = (bid, mediaType) => { + let mediaTypes = bid.mediaTypes; + if (mediaType === 'video') { + let size = []; + if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { + size = [ + mediaTypes.video.w, + mediaTypes.video.h + ]; + } else if (Array.isArray(utils.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]; + } + return size; + } + let sizes = []; + if (Array.isArray(mediaTypes.banner.sizes)) { + sizes = mediaTypes.banner.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes + } else { + utils.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/bizzclickBidAdapter.md b/modules/bizzclickBidAdapter.md index 7dfa458b34c..6fc1bebf546 100644 --- a/modules/bizzclickBidAdapter.md +++ b/modules/bizzclickBidAdapter.md @@ -14,14 +14,91 @@ Module that connects to BizzClick SSP demand sources ``` var adUnits = [{ code: 'placementId', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, bids: [{ bidder: 'bizzclick', params: { - placementId: 0, - type: 'banner' + placementId: 'hash', + accountId: 'accountId' } }] + }, + { + code: 'native_example', + // sizes: [[1, 1]], + 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: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: { + minduration:0, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + w:1920, + h:1080, + protocols:[ + 2 + ], + linearity:1, + api:[ + 1, + 2 + ] + } }, + bids: [ + { + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' } + } + ] + } ]; ``` \ No newline at end of file diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js new file mode 100644 index 00000000000..2f4cb9beac6 --- /dev/null +++ b/modules/bliinkBidAdapter.js @@ -0,0 +1,309 @@ +// 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 NATIVE = 'native' +const BANNER = 'banner' + +const supportedMediaTypes = [BANNER, VIDEO, NATIVE] +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() + const xml = parser.parseFromString(content, 'text/xml') + + if (xml && + xml.getElementsByTagName('VAST')[0] && + xml.getElementsByTagName('VAST')[0].tagName === 'VAST') { + return xml + } + + return null +} + +/** + * @param bidRequest + * @param bliinkCreative + * @return {{cpm, netRevenue: boolean, ad: string, requestId, width: number, currency: string, mediaType: string, vastXml, ttl: number, height: number}|null} + */ +export const buildBid = (bidRequest, bliinkCreative) => { + if (!bidRequest && !bliinkCreative) return null + + const body = { + requestId: bidRequest.bidId, + cpm: bliinkCreative.price, + creativeId: bliinkCreative.creativeId, + currency: 'EUR', + netRevenue: false, + width: 1, + height: 1, + 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'] + + return Object.assign(body, { + currency: bliinkCreative.currency, + width: 1, + height: 1, + mediaType: VIDEO, + ad: '', + vastXml: bliinkCreative.content, + }) +} + +/** + * @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(','), + 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') && (!request.params)) { + return [] + } + + const body = serverResponse.body + const serverBody = request.params + + const xml = parseXML(body) + + if (xml) { + const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0] + const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0] + const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0] + + const creative = { + content: body, + price: (price && price.textContent) || 0, + currency: (currency && currency.textContent) || 'EUR', + creativeId: creativeId || 0, + media_type: 'video', + } + + return buildBid(serverBody.bids[0], creative); + } + + return [] +} + +/** + * @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..ae0d4275396 --- /dev/null +++ b/modules/bliinkBidAdapter.md @@ -0,0 +1,71 @@ +# 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: '14f30eca-85d2-11e8-9eed-0242ac120007' + } + } + ] + } +] +``` + +## 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], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1 + } + }, + bids: [ + { + bidder: 'bliink', + params: { + tagId: '41', + placement: 'video', + } + } + ] + } +] +``` diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index 4d40d931e1d..3d4eeb058b3 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -7,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', @@ -17,17 +18,18 @@ const BB_CONSTANTS = { DEFAULT_TTL: 300, DEFAULT_WIDTH: 768, DEFAULT_HEIGHT: 432, - DEFAULT_NET_REVENUE: true + DEFAULT_NET_REVENUE: true, + 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'] }; // Aliasing const getConfig = config.getConfig; // Helper Functions -export const BB_HELPERS = { +const BB_HELPERS = { addSiteAppDevice: function(request, pageUrl) { - if (!request) return; - if (typeof getConfig('app') === 'object') request.app = getConfig('app'); else { request.site = {}; @@ -41,21 +43,15 @@ export const BB_HELPERS = { if (!request.device.h) request.device.h = window.innerHeight; }, addSchain: function(request, validBidRequests) { - if (!request) return; - const schain = utils.deepAccess(validBidRequests, '0.schain'); if (schain) request.source.ext = { schain: schain }; }, addCurrency: function(request) { - if (!request) return; - const adServerCur = getConfig('currency.adServerCurrency'); if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur]; else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; }, addUserIds: function(request, validBidRequests) { - if (!request) return; - const bidUserId = utils.deepAccess(validBidRequests, '0.userId'); const eids = createEidsArray(bidUserId); @@ -63,10 +59,6 @@ export const BB_HELPERS = { utils.deepSetValue(request, 'user.ext.eids', eids); } }, - addDigiTrust: function(request, bidRequests) { - const digiTrust = BB_HELPERS.getDigiTrustParams(bidRequests && bidRequests[0]); - if (digiTrust) utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); - }, substituteUrl: function (url, publication, renderer) { return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer); }, @@ -79,41 +71,60 @@ export const BB_HELPERS = { getRendererUrl: function(publication, renderer) { return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); }, - getDigiTrustParams: function(bidRequest) { - const digiTrustId = BB_HELPERS.getDigiTrustId(bidRequest); + transformVideoParams: function(videoParams, videoParamsExt) { + videoParams = utils.deepClone(videoParams); - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) return null; - return { - id: digiTrustId.id, - keyv: digiTrustId.keyv - } - }, - getDigiTrustId: function(bidRequest) { - const bidRequestDigiTrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigiTrust) return bidRequestDigiTrust; + let playerSize = videoParams.playerSize || [BB_CONSTANTS.DEFAULT_WIDTH, BB_CONSTANTS.DEFAULT_HEIGHT]; + if (Array.isArray(playerSize[0])) playerSize = playerSize[0]; + + videoParams.w = playerSize[0]; + videoParams.h = playerSize[1]; + videoParams.placement = 3; - const digiTrustUser = getConfig('digiTrustId'); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + if (videoParamsExt) videoParams = Object.assign(videoParams, videoParamsExt); + + const videoParamsProperties = Object.keys(videoParams); + + videoParamsProperties.forEach(property => { + if (BB_CONSTANTS.VIDEO_PARAMS.indexOf(property) === -1) delete videoParams[property]; + }); + + return videoParams; }, transformRTBToPrebidProps: function(bid, serverResponse) { - bid.cpm = bid.price; delete bid.price; - bid.bidId = bid.impid; - bid.requestId = bid.impid; delete bid.impid; - bid.width = bid.w || BB_CONSTANTS.DEFAULT_WIDTH; - bid.height = bid.h || BB_CONSTANTS.DEFAULT_HEIGHT; + const bidObject = { + cpm: bid.price, + currency: serverResponse.cur, + netRevenue: BB_CONSTANTS.DEFAULT_NET_REVENUE, + bidId: bid.impid, + requestId: bid.impid, + creativeId: bid.crid, + mediaType: VIDEO, + width: bid.w || BB_CONSTANTS.DEFAULT_WIDTH, + height: bid.h || BB_CONSTANTS.DEFAULT_HEIGHT, + ttl: BB_CONSTANTS.DEFAULT_TTL + }; + + const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); + const extPrebidCache = utils.deepAccess(bid, 'ext.prebid.cache'); + + if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) { + bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId; + bidObject.vastUrl = extPrebidCache.vastXml.url; + } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { + bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; + bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; + } if (bid.adm) { - bid.ad = bid.adm; - bid.vastXml = bid.adm; - delete bid.adm; + bidObject.ad = bid.adm; + bidObject.vastXml = bid.adm; } - if (bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 - bid.vastUrl = bid.nurl; - delete bid.nurl; + 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; } - bid.netRevenue = BB_CONSTANTS.DEFAULT_NET_REVENUE; - bid.creativeId = bid.crid; delete bid.crid; - bid.currency = serverResponse.cur; - bid.ttl = BB_CONSTANTS.DEFAULT_TTL; + bidObject.meta = bid.meta || {}; + if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } + return bidObject; }, }; @@ -132,20 +143,16 @@ const BB_RENDERER = { return; } - const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); + if (!(window.bluebillywig && window.bluebillywig.renderers)) { + utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`); + return; + } + const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); const ele = document.getElementById(bid.adUnitCode); // NB convention + const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId); - let renderer; - - for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { - if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { - renderer = window.bluebillywig.renderers[rendererIndex]; - break; - } - } - - if (renderer) renderer.bootstrap(config, ele); + if (renderer) renderer.bootstrap(config, ele, bid.rendererSettings || {}); else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); }, newRenderer: function(rendererUrl, adUnitCode) { @@ -212,9 +219,8 @@ export const spec = { utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); return false; } else { - for (let connectionIndex = 0; connectionIndex < bid.params.connections.length; connectionIndex++) { - const connection = bid.params.connections[connectionIndex]; - if (!bid.params.hasOwnProperty(connection)) { + 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); return false; } @@ -225,6 +231,16 @@ export const spec = { 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); + 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); + 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); @@ -245,20 +261,21 @@ export const spec = { buildRequests(validBidRequests, bidderRequest) { const imps = []; - for (let validBidRequestIndex = 0; validBidRequestIndex < validBidRequests.length; validBidRequestIndex++) { - const validBidRequest = validBidRequests[validBidRequestIndex]; - const _this = this; + validBidRequests.forEach(validBidRequest => { + if (!this.syncStore.publicationName) this.syncStore.publicationName = validBidRequest.params.publicationName; + if (!this.syncStore.accountId) this.syncStore.accountId = validBidRequest.params.accountId; - const ext = validBidRequest.params.connections.reduce(function(extBuilder, connection) { + const ext = validBidRequest.params.connections.reduce((extBuilder, connection) => { extBuilder[connection] = validBidRequest.params[connection]; - if (_this.syncStore.bidders.indexOf(connection) === -1) _this.syncStore.bidders.push(connection); + if (this.syncStore.bidders.indexOf(connection) === -1) this.syncStore.bidders.push(connection); return extBuilder; }, {}); - imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: utils.deepAccess(validBidRequest, 'mediaTypes.video') }); - } + const videoParams = BB_HELPERS.transformVideoParams(utils.deepAccess(validBidRequest, 'mediaTypes.video'), utils.deepAccess(validBidRequest, 'params.video')); + imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams }); + }); const request = { id: bidderRequest.auctionId, @@ -293,7 +310,6 @@ export const spec = { BB_HELPERS.addSchain(request, validBidRequests); BB_HELPERS.addCurrency(request); BB_HELPERS.addUserIds(request, validBidRequests); - BB_HELPERS.addDigiTrust(request, validBidRequests); return { method: 'POST', @@ -311,74 +327,44 @@ export const spec = { const bids = []; - for (let seatbidIndex = 0; seatbidIndex < serverResponse.seatbid.length; seatbidIndex++) { - const seatbid = serverResponse.seatbid[seatbidIndex]; - if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue; - for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { - const bid = seatbid.bid[bidIndex]; - BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - - let bidParams; - for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { - if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === bid.bidId) { - bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; - } - } + serverResponse.seatbid.forEach(seatbid => { + if (!seatbid.bid || !Array.isArray(seatbid.bid)) return; + seatbid.bid.forEach(bid => { + bid = BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - if (bidParams) { - bid.publicationName = bidParams.publicationName; - bid.rendererCode = bidParams.rendererCode; - bid.accountId = bidParams.accountId; - } + const bidParams = find(request.bidderRequest.bids, bidderRequestBid => bidderRequestBid.bidId === bid.bidId).params; + bid.publicationName = bidParams.publicationName; + bid.rendererCode = bidParams.rendererCode; + bid.accountId = bidParams.accountId; + bid.rendererSettings = bidParams.rendererSettings; const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode); - bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode); bids.push(bid); - } - } + }); + }); return bids; }, getUserSyncs(syncOptions, serverResponses, gdpr) { - if (!serverResponses || !serverResponses.length) return []; if (!syncOptions.iframeEnabled) return []; const queryString = []; - let accountId; - let publication; - - const serverResponse = serverResponses[0]; - if (!serverResponse.body || !serverResponse.body.seatbid) return []; - - for (let seatbidIndex = 0; seatbidIndex < serverResponse.body.seatbid.length; seatbidIndex++) { - const seatbid = serverResponse.body.seatbid[seatbidIndex]; - for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { - const bid = seatbid.bid[bidIndex]; - accountId = bid.accountId || null; - publication = bid.publicationName || null; - - if (publication && accountId) break; - } - if (publication && accountId) break; - } - - if (!publication || !accountId) return []; if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`); if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`); if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`); - queryString.push(`accountId=${accountId}`); + queryString.push(`accountId=${this.syncStore.accountId}`); queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`); queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`); if (DEV_MODE) queryString.push('bbpbs_debug=true'); // NB syncUrl by default starts with ?pub=$$PUBLICATION - const syncUrl = `${BB_HELPERS.getSyncUrl(publication)}&${queryString.join('&')}`; + const syncUrl = `${BB_HELPERS.getSyncUrl(this.syncStore.publicationName)}&${queryString.join('&')}`; return [{ type: 'iframe', diff --git a/modules/trendqubeBidAdapter.js b/modules/boldwinBidAdapter.js similarity index 51% rename from modules/trendqubeBidAdapter.js rename to modules/boldwinBidAdapter.js index d0364b4acec..66b4cbfd77b 100644 --- a/modules/trendqubeBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -2,8 +2,9 @@ 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'; +const BIDDER_CODE = 'boldwin'; +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 || @@ -22,12 +23,29 @@ function isBidResponseValid(bid) { } } +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.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) => { @@ -62,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 { @@ -92,11 +132,21 @@ 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); } } return response; }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: SYNC_URL + }]; + } }; registerBidder(spec); diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md new file mode 100644 index 00000000000..5e2a5b139b3 --- /dev/null +++ b/modules/boldwinBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: boldwin Bidder Adapter +Module Type: boldwin Bidder Adapter +``` + +# Description + +Module that connects to boldwin demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 'testBanner', + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 'testVideo', + } + } + ] + } + ]; +``` diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js new file mode 100644 index 00000000000..7ea3eb9cd9b --- /dev/null +++ b/modules/braveBidAdapter.js @@ -0,0 +1,267 @@ +import * as utils 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: utils.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 || utils.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 (utils.isStr(bid.nurl) && bid.nurl !== '') { + utils.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 0303e4f74bd..2034a59242e 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -4,8 +4,8 @@ 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.2'; +const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb='; +const BIDDER_VERSION = '1.1.0'; export const spec = { code: BIDDER_CODE, @@ -19,11 +19,13 @@ export const spec = { */ isBidRequestValid: function (bid) { let valid = false; - - if (bid && bid.params && bid.params.ChannelID) { - valid = true; + if (bid && bid.params) { + if ((bid.params.cid) && (typeof bid.params.cid === 'number')) { + valid = true; + } else if (bid.params.ChannelID) { + valid = true; + } } - return valid; }, @@ -35,16 +37,37 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const adUnits = []; + var bidderUrl = REQUEST_ENDPOINT + Math.random(); + var userIds; + utils._each(validBidRequests, function (bid) { - adUnits.push({ - ChannelID: bid.params.ChannelID, - adUnitCode: bid.adUnitCode, - mediaTypes: bid.mediaTypes || { - banner: { - sizes: bid.sizes - } - } - }); + userIds = bid.userId; + + if (bid.params.cid) { + adUnits.push({ + cid: bid.params.cid, + adUnitCode: bid.adUnitCode, + requestId: bid.bidId, + mediaTypes: bid.mediaTypes || { + banner: { + sizes: bid.sizes + } + }, + userIds: userIds || {} + }); + } else { + adUnits.push({ + ChannelID: bid.params.ChannelID, + adUnitCode: bid.adUnitCode, + requestId: bid.bidId, + mediaTypes: bid.mediaTypes || { + banner: { + sizes: bid.sizes + } + }, + userIds: userIds || {} + }); + } }); let topUrl = ''; @@ -54,7 +77,7 @@ export const spec = { return { method: 'POST', - url: REQUEST_ENDPOINT, + url: bidderUrl, data: { version: { prebid: '$prebid.version$', @@ -63,7 +86,8 @@ export const spec = { inIframe: utils.inIframe(), url: topUrl, referrer: getTopWindowReferrer(), - adUnits: adUnits + adUnits: adUnits, + refererInfo: bidderRequest.refererInfo, }, validBidRequests: validBidRequests }; @@ -143,6 +167,10 @@ export const spec = { bidResponse.currency = matchedResponse.currency; bidResponse.mediaType = matchedResponse.mediaType; + if (matchedResponse.adomain) { + utils.deepSetValue(bidResponse, 'meta.advertiserDomains', Array.isArray(matchedResponse.adomain) ? matchedResponse.adomain : [matchedResponse.adomain]); + } + // check required parameters by matchedResponse.mediaType switch (matchedResponse.mediaType) { case BANNER: diff --git a/modules/bridgewellBidAdapter.md b/modules/bridgewellBidAdapter.md index 6bcab4b8820..97e11f6eaf9 100644 --- a/modules/bridgewellBidAdapter.md +++ b/modules/bridgewellBidAdapter.md @@ -20,7 +20,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ' + cid: 12345 } }] }, { @@ -33,7 +33,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + cid: 56789 } }] }, { @@ -70,7 +70,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + cid: 2394 } }] }]; diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js new file mode 100644 index 00000000000..602e621632c --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.js @@ -0,0 +1,253 @@ +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?sid='; +const SYNC_URL = 'https://console.brightmountainmedia.com:8443/cookieSync'; +const CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['brightmountainmedia'], + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: (bid) => { + 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 requestData = []; + let size = [0, 0]; + let oRTBRequest = { + at: 2, + site: buildSite(bidderRequest), + device: buildDevice(), + cur: [CURRENCY], + tmax: 1000, + regs: buildRegs(bidderRequest), + user: {}, + source: {}, + }; + + validBidRequests.forEach((bid) => { + oRTBRequest['id'] = utils.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 (utils.deepAccess(bid, 'mediaTypes.banner')) { + if (bid.mediaTypes.banner.sizes) { + size = bid.mediaTypes.banner.sizes[0]; + } + + oRTBRequest.imp[0].banner = { + h: size[0], + w: size[1], + } + } else { + if (bid.mediaTypes.video.playerSize) { + size = bid.mediaTypes.video.playerSize[0]; + } + + 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, + } + } + + 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, { bidRequest }) => { + let bidResponse = []; + 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) { + utils.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: utils.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; + }, + + getUserSyncs: (syncOptions) => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_URL + }]; + } + }, + +}; + +registerBidder(spec); + +function buildSite(bidderRequest) { + let site = { + name: window.location.hostname, + publisher: { + domain: window.location.hostname, + } + }; + + if (bidderRequest && bidderRequest.refererInfo) { + utils.deepSetValue( + site, + 'page', + bidderRequest.refererInfo.referer.href ? bidderRequest.refererInfo.referer.href : '', + ); + utils.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) { + utils.deepSetValue( + regs, + 'ext.gdpr', + bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + ); + utils.deepSetValue( + regs, + 'ext.gdprConsentString', + bidderRequest.gdprConsent.consentString || 'ALL', + ); + } + + if (bidderRequest && bidderRequest.uspConsent) { + utils.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/brightMountainMediaBidAdapter.md b/modules/brightMountainMediaBidAdapter.md new file mode 100644 index 00000000000..97cebe5b633 --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: Bright Mountain Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev@brightmountainmedia.com +``` + +# Description + +Connects to Bright Mountain Media exchange for bids. + +Bright Mountain Media bid adapter currently supports Banner and Video. + +# Sample Ad Unit: For Publishers + +## Sample Banner only Ad Unit + +``` +var adUnits = [ + { + code: 'postbid_iframe', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + "bidder": "bmtm", + "params": { + "placement_id": 1 + } + } + ] + } + ]; +``` + +## Sample Video only Ad Unit: Outstream + +``` +var adUnits = [ + { + code: 'postbid_iframe_video', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + ... // Additional ORTB video params + } + }, + bids: [ + { + bidder: "bmtm", + params: { + placement_id: 1 + } + } + ], + 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 + }); + }); + } + } + } +]; + +``` + +## Sample Video only Ad Unit: Instream + +``` +var adUnits = [ + { + code: 'postbid_iframe_video', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + ... // Additional ORTB video params + }, + }, + bids: [ + { + bidder: "bmtm", + params: { + placement_id: 1 + } + } + ] + } +]; + +``` diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index a4b013a2fe2..8299a2331cb 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -46,7 +46,7 @@ function buildRequests(bidReqs, bidderRequest) { }, tagid: String(bid.adUnitCode) }; - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + const bidFloor = _getBidFloor(bid); if (bidFloor) { imp.bidfloor = bidFloor; } @@ -70,6 +70,11 @@ function buildRequests(bidReqs, bidderRequest) { tmax: config.getConfig('bidderTimeout') }; + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + utils.deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + return { method: 'POST', url: URL, @@ -117,7 +122,10 @@ function interpretResponse(serverResponse) { netRevenue: true, mediaType: BANNER, ad: _getAdMarkup(brightcomBid), - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: brightcomBid && brightcomBid.adomain ? brightcomBid.adomain : [] + } }); }); } @@ -243,4 +251,20 @@ function _getPercentInView(element, topWin, { w, h } = {}) { return 0; } +function _getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.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 17a39e96aad..3bf416957d2 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -8,6 +8,7 @@ import * as utils 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'; /** @type {Submodule} */ export const britepoolIdSubmodule = { @@ -28,10 +29,12 @@ export const britepoolIdSubmodule = { /** * Performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [submoduleConfig] + * @param {ConsentData|undefined} consentData * @returns {function(callback:function)} */ - getId(submoduleConfigParams, consentData) { + getId(submoduleConfig, consentData) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams(submoduleConfigParams, consentData); let getterResponse = null; if (typeof getter === 'function') { @@ -44,6 +47,9 @@ export const britepoolIdSubmodule = { }; } } + if (utils.isEmpty(params)) { + utils.triggerPixel(PIXEL); + } // Return for async operation return { callback: function(callback) { @@ -79,13 +85,17 @@ export const britepoolIdSubmodule = { }, /** * Helper method to create params for our API call - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleParams} [submoduleConfigParams] + * @param {ConsentData|undefined} consentData * @returns {object} Object with parsed out params */ createParams(submoduleConfigParams, consentData) { + const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; + const gdprConsentString = hasGdprData ? consentData.consentString : undefined; let errors = []; const headers = {}; - let params = Object.assign({}, submoduleConfigParams); + const dynamicVars = typeof britepool_pubparams !== 'undefined' ? britepool_pubparams : {}; // eslint-disable-line camelcase, no-undef + let params = Object.assign({}, submoduleConfigParams, dynamicVars); if (params.getter) { // Custom getter will not require other params if (typeof params.getter !== 'function') { @@ -98,7 +108,7 @@ export const britepoolIdSubmodule = { headers['x-api-key'] = params.api_key; } } - const url = params.url || 'https://api.britepool.com/v1/britepool/id'; + const url = params.url || `https://api.britepool.com/v1/britepool/id${gdprConsentString ? '?gdprString=' + encodeURIComponent(gdprConsentString) : ''}`; const getter = params.getter; delete params.api_key; delete params.url; diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md index 89287aed7ca..72edbe2324b 100644 --- a/modules/britepoolIdSystem.md +++ b/modules/britepoolIdSystem.md @@ -4,10 +4,10 @@ BritePool User ID Module. For assistance setting up your module please contact u ### Prebid Params -Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. +Individual params may be set for the BritePool User ID Submodule. ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: 'britepoolId', storage: { diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 9317786fb8d..4ee338e94cc 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,30 +13,23 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName - * @property {?number} auctionDelay - * @property {?number} timeout */ -import {config} from '../src/config.js'; import * as utils 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 {getStorageManager} from '../src/storageManager.js'; import find from 'core-js-pure/features/array/find.js'; const storage = getStorageManager(); -/** @type {string} */ -const MODULE_NAME = 'realTimeData'; -/** @type {number} */ -const DEF_TIMEOUT = 1000; /** @type {ModuleParams} */ let _moduleParams = {}; /** @type {null|Object} */ -let _data = null; -/** @type {null | function} */ -let _dataReadyCallback = null; +let _predictionsData = null; +/** @type {string} */ +const DEF_KEYNAME = 'browsiViewability'; /** * add browsi script to page @@ -61,7 +54,7 @@ export function addBrowsiTag(data) { * collect required data from page * send data to browsi server to get predictions */ -function collectData() { +export function collectData() { const win = window.top; const doc = win.document; let browsiData = null; @@ -86,61 +79,33 @@ function collectData() { } export function setData(data) { - _data = data; - - if (typeof _dataReadyCallback === 'function') { - _dataReadyCallback(_data); - _dataReadyCallback = null; - } -} - -/** - * wait for data from server - * call callback when data is ready - * @param {function} callback - */ -function waitForData(callback) { - if (_data) { - _dataReadyCallback = null; - callback(_data); - } else { - _dataReadyCallback = callback; - } + _predictionsData = data; } -/** - * filter server data according to adUnits received - * call callback (onDone) when data is ready - * @param {adUnit[]} adUnits - * @param {function} onDone callback function - */ -function sendDataToModule(adUnits, onDone) { +function sendDataToModule(adUnitsCodes) { try { - waitForData(_predictionsData => { - const _predictions = _predictionsData.p; - if (!_predictions || !Object.keys(_predictions).length) { - return onDone({}); + const _predictions = (_predictionsData && _predictionsData.p) || {}; + return adUnitsCodes.reduce((rp, adUnitCode) => { + if (!adUnitCode) { + return rp } - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - const adSlot = getSlotByCode(adUnitCode); - const identifier = adSlot ? getMacroId(_predictionsData.pmd, adSlot) : adUnitCode; - const predictionData = _predictions[identifier]; - if (!predictionData) { return rp } - - if (predictionData.p) { - if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { - return rp; - } - rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + const adSlot = getSlotByCode(adUnitCode); + const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode; + const predictionData = _predictions[identifier]; + rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']); + if (!predictionData) { + return rp + } + if (predictionData.p) { + if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { + return rp; } - return rp; - }, {}); - return onDone(dataToReturn); - }); + rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + } + return rp; + }, {}); } catch (e) { - onDone({}); + return {}; } } @@ -160,7 +125,7 @@ function getAllSlots() { function getKVObject(p, keyName) { const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2); let prObject = {}; - prObject[((_moduleParams['keyName'] || keyName).toString())] = prValue.toString(); + prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString(); return prObject; } /** @@ -231,7 +196,7 @@ function evaluate(macro, divId, adUnit, replacer) { * @param {string} url server url with query params */ function getPredictionsFromServer(url) { - let ajax = ajaxBuilder(_moduleParams.auctionDelay || _moduleParams.timeout || DEF_TIMEOUT); + let ajax = ajaxBuilder(); ajax(url, { @@ -283,30 +248,23 @@ export const browsiSubmodule = { /** * get data and send back to realTimeData module * @function - * @param {adUnit[]} adUnits - * @param {function} onDone + * @param {string[]} adUnitsCodes */ - getData: sendDataToModule + getTargetingData: sendDataToModule, + init: init, }; -export function init(config) { - const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { - try { - _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter( - pr => pr.name && pr.name.toLowerCase() === 'browsi')[0].params; - _moduleParams.auctionDelay = realTimeData.auctionDelay; - _moduleParams.timeout = realTimeData.timeout; - } catch (e) { - _moduleParams = {}; - } - if (_moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { - confListener(); - collectData(); - } else { - utils.logError('missing params for Browsi provider'); - } - }); +function init(moduleConfig) { + _moduleParams = moduleConfig.params; + if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { + collectData(); + } else { + utils.logError('missing params for Browsi provider'); + } + return true; } -submodule('realTimeData', browsiSubmodule); -init(config); +function registerSubModule() { + submodule('realTimeData', browsiSubmodule); +} +registerSubModule(); diff --git a/modules/browsiRtdProvider.md b/modules/browsiRtdProvider.md new file mode 100644 index 00000000000..9eed4b2b2d4 --- /dev/null +++ b/modules/browsiRtdProvider.md @@ -0,0 +1,55 @@ +# Overview + +The Browsi RTD module provides viewability predictions for ad slots on the page. +To use this module, you’ll need to work with [Browsi](https://gobrowsi.com/) to get an account and receive instructions on how to set up your pages and ad server. + +# Configurations + +Compile the Browsi RTD Provider into your Prebid build: + +`gulp build --modules=browsiRtdProvider` + + +Configuration example for using RTD module with `browsi` provider +```javascript + pbjs.setConfig({ + "realTimeData": { + "auctionDelay": 1000, + dataProviders:[{ + "name": "browsi", + "waitForIt": "true" + "params": { + "url": "yield-manager.browsiprod.com", + "siteKey": "browsidemo", + "pubKey": "browsidemo" + "keyName":"bv" + } + }] + } + }); +``` + +#Params + +Contact Browsi to get required params + +| param name | type |Scope | Description | +| :------------ | :------------ | :------- | :------- | +| url | string | required | Browsi server URL | +| siteKey | string | required | Site key | +| pubKey | string | required | Publisher key | +| keyName | string | optional | Ad unit targeting key | + + +#Output +`getTargetingData` function will return expected viewability prediction in the following structure: +```json +{ + "adUnitCode":{ + "browsiViewability":"0.6" + }, + "adUnitCode2":{ + "browsiViewability":"0.9" + } +} +``` diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 3f327e62121..46d0dfe3590 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -90,6 +90,7 @@ 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'); @@ -110,7 +111,10 @@ export const spec = { creativeId: sCreativeID, currency: sCurrency, netRevenue: bNetRevenue, - ad: sAd + ad: sAd, + meta: { + advertiserDomains: sAdomains + } }; bidResponses.push(bidResponse); } else { diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index f87607657c3..db4863f7503 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -1,6 +1,6 @@ import * as utils 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); }, /** 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..0f1d59024b7 --- /dev/null +++ b/modules/byDataAnalyticsAdapter.js @@ -0,0 +1,311 @@ +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'; +import * as utils from '../src/utils.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 = utils.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) { + utils.logInfo(buildLogMessage(message), meta); +} + +function logError(message) { + utils.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 ee15d6bb3ec..2160e539040 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -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 = utils.deepAccess(bid, 'mediaTypes.video.protocols') || utils.deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS + placement.video.mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes') || utils.deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES + placement.video.playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod') || utils.deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS + placement.video.skip = utils.deepAccess(bid, 'mediaTypes.video.skip') || utils.deepAccess(bid, 'params.video.skip') || 0 + if (placement.video.skip === 1 && (utils.deepAccess(bid, 'mediaTypes.video.skipafter') || utils.deepAccess(bid, 'params.video.skipafter'))) { + placement.video.skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter') || utils.deepAccess(bid, 'params.video.skipafter') } } @@ -120,6 +120,11 @@ function _buildResponse (bid, currency, ttl) { currency: currency } + resp.meta = {}; + if (bid.adomain && bid.adomain.length > 0) { + resp.meta.advertiserDomains = bid.adomain; + } + if (bid.ext.type === 'video') { resp.vastXml = bid.adm } else { 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/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index a8f37450d68..7b36117d404 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; export const helper = { getTopWindowDomain: function (url) { @@ -112,14 +113,14 @@ export const spec = { id: transactionId, instl: params.instl === 1 ? 1 : 0, tagid: adUnitCode, - bidfloor: params.bidfloor || 0, + bidfloor: 0, bidfloorcur: 'USD', secure: 1 }; const hasFavoredMediaType = params.favoredMediaType && - this.supportedMediaTypes.includes(params.favoredMediaType); + includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index 20408fe9177..2e758c509f7 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -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/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js new file mode 100644 index 00000000000..a6241980e06 --- /dev/null +++ b/modules/cointrafficBidAdapter.js @@ -0,0 +1,101 @@ +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 = 'cointraffic'; +const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const DEFAULT_CURRENCY = 'EUR'; +const ALLOWED_CURRENCIES = [ + 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', + 'AUD', 'BRL', 'CAD', 'CNY', 'HKD', 'IDR', 'ILS', 'INR', 'KRW', 'MXN', 'MYR', 'NZD', 'PHP', 'SGD', 'THB', 'ZAR', +]; + +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.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param validBidRequests + * @param bidderRequest + * @return Array Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const sizes = utils.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); + return; + } + + const payload = { + placementId: bidRequest.params.placementId, + currency: currency, + sizes: sizes, + bidId: bidRequest.bidId, + referer: bidderRequest.refererInfo.referer, + }; + + 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. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (utils.isEmpty(response)) { + return bidResponses; + } + + const bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + currency: response.currency, + netRevenue: response.netRevenue, + width: response.width, + height: response.height, + creativeId: response.creativeId, + ttl: response.ttl, + ad: response.ad, + meta: { + advertiserDomains: response.adomain && response.adomain.length ? response.adomain : [], + mediaType: response.mediaType + } + }; + + bidResponses.push(bidResponse); + + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/cointrafficBidAdapter.md b/modules/cointrafficBidAdapter.md new file mode 100644 index 00000000000..fcbcad1cad7 --- /dev/null +++ b/modules/cointrafficBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Cointraffic Bidder Adapter +Module Type: Cointraffic Adapter +Maintainer: tech@cointraffic.io +``` + +# Description +The Cointraffic client module makes it easy to implement Cointraffic directly into your website. To get started, simply replace the ``placementId`` with your assigned tracker key. This is dependent on the size required by your account dashboard. +We support response in different currencies. Supported currencies listed [here](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html). + +For additional information on this module, please contact us at ``support@cointraffic.io``. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + } + }] + }]; +``` diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 240a3f1fcde..e1c2b7d01b2 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -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 baa60a76a0d..52865b8085d 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -56,15 +56,8 @@ export const spec = { * @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); - }; + const winTop = utils.getWindowTop(); + const location = winTop.location; let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -94,19 +87,51 @@ export const spec = { bidId: bid.bidId, sizes: bid.mediaTypes[traff].sizes, traffic: traff, - eids: [] + eids: [], + floor: {} }; + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } if (bid.schain) { placement.schain = bid.schain; } 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 { @@ -129,6 +154,9 @@ 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); } } diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index d95080546c2..8797c648c95 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -13,19 +13,18 @@ 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' + } + }] + ]; ``` diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js new file mode 100644 index 00000000000..a81d07e63b5 --- /dev/null +++ b/modules/concertAnalyticsAdapter.js @@ -0,0 +1,120 @@ +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'; + +// We only want to send about 1% of events for sampling purposes +const SAMPLE_RATE_PERCENTAGE = 1 / 100; +const pageIncludedInSample = sampleAnalytics(); + +const url = 'https://bids.concert.io/analytics'; + +const { + EVENTS: { + BID_RESPONSE, + BID_WON, + AUCTION_END + } +} = CONSTANTS; + +let queue = []; + +let concertAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ eventType, args }) { + switch (eventType) { + case BID_RESPONSE: + if (args.bidder !== 'concert') break; + queue.push(mapBidEvent(eventType, args)); + break; + + case BID_WON: + if (args.bidder !== 'concert') break; + queue.push(mapBidEvent(eventType, args)); + break; + + case AUCTION_END: + // Set a delay, as BID_WON events will come after AUCTION_END events + setTimeout(() => sendEvents(), 3000); + break; + + default: + break; + } + } +}); + +function mapBidEvent(eventType, args) { + const { adId, auctionId, cpm, creativeId, width, height, timeToRespond } = args; + const [gamCreativeId, concertRequestId] = getConcertRequestId(creativeId); + + const payload = { + event: eventType, + concert_rid: concertRequestId, + adId, + auctionId, + creativeId: gamCreativeId, + position: args.adUnitCode, + url: window.location.href, + cpm, + width, + height, + timeToRespond + } + + return payload; +} + +/** + * In order to pass back the concert_rid from CBS, it is tucked into the `creativeId` + * slot in the bid response and combined with a pipe `|`. This method splits the creative ID + * and the concert_rid. + * + * @param {string} creativeId + */ +function getConcertRequestId(creativeId) { + if (!creativeId || creativeId.indexOf('|') < 0) return [null, null]; + + return creativeId.split('|'); +} + +function sampleAnalytics() { + return Math.random() <= SAMPLE_RATE_PERCENTAGE; +} + +function sendEvents() { + concertAnalytics.eventsStorage = queue; + + if (!queue.length) return; + + if (!pageIncludedInSample) { + utils.logMessage('Page not included in sample for Concert Analytics'); + return; + } + + try { + const body = JSON.stringify(queue); + ajax(url, () => queue = [], body, { + contentType: 'application/json', + method: 'POST' + }); + } catch (err) { utils.logMessage('Concert Analytics error') } +} + +// save the base class function +concertAnalytics.originEnableAnalytics = concertAnalytics.enableAnalytics; +concertAnalytics.eventsStorage = []; + +// override enableAnalytics so we can get access to the config passed in from the page +concertAnalytics.enableAnalytics = function (config) { + concertAnalytics.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: concertAnalytics, + code: 'concert' +}); + +export default concertAnalytics; diff --git a/modules/concertAnalyticsAdapter.md b/modules/concertAnalyticsAdapter.md new file mode 100644 index 00000000000..7c9b6d22703 --- /dev/null +++ b/modules/concertAnalyticsAdapter.md @@ -0,0 +1,11 @@ +# Overview + +``` +Module Name: Concert Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@concert.io +``` + +# Description + +Analytics adapter for concert. \ No newline at end of file diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js new file mode 100644 index 00000000000..60634151aba --- /dev/null +++ b/modules/concertBidAdapter.js @@ -0,0 +1,212 @@ + +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js' + +const BIDDER_CODE = 'concert'; +const CONCERT_ENDPOINT = 'https://bids.concert.io'; +const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; + +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: function(bid) { + if (!bid.params.partnerId) { + utils.logWarn('Missing partnerId bid parameter'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + utils.logMessage(validBidRequests); + utils.logMessage(bidderRequest); + let payload = { + meta: { + prebidVersion: '$prebid.version$', + pageUrl: bidderRequest.refererInfo.referer, + screen: [window.screen.width, window.screen.height].join('x'), + debug: utils.debugTurnedOn(), + uid: getUid(bidderRequest), + optedOut: hasOptedOutOfPersonalization(), + adapterVersion: '1.1.1', + uspConsent: bidderRequest.uspConsent, + gdprConsent: bidderRequest.gdprConsent + } + } + + payload.slots = validBidRequests.map(bidRequest => { + let slot = { + name: bidRequest.adUnitCode, + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + sizes: bidRequest.params.sizes || bidRequest.sizes, + partnerId: bidRequest.params.partnerId, + slotType: bidRequest.params.slotType, + adSlot: bidRequest.params.slot || bidRequest.adUnitCode, + placementId: bidRequest.params.placementId || '', + site: bidRequest.params.site || bidderRequest.refererInfo.referer + } + + return slot; + }); + + utils.logMessage(payload); + + return { + method: 'POST', + url: `${CONCERT_ENDPOINT}/bids/prebid`, + data: JSON.stringify(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) { + utils.logMessage(serverResponse); + utils.logMessage(bidRequest); + + const serverBody = serverResponse.body; + + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + let bidResponses = []; + + bidResponses = serverBody.bids.map(bid => { + return { + requestId: bid.bidId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + ad: bid.ad, + ttl: bid.ttl, + meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + currency: bid.currency + } + }); + + if (utils.debugTurnedOn() && serverBody.debug) { + utils.logMessage(`CONCERT`, serverBody.debug); + } + + utils.logMessage(bidResponses); + 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} object GDPR consent object. + * @param {uspConsent} string US Privacy String. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { + let params = []; + + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); + } + if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + if (uspConsent && (typeof uspConsent === 'string')) { + params.push(`usp_consent=${uspConsent}`); + } + + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') + }); + } + return syncs; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function(data) { + utils.logMessage('concert bidder timed out'); + utils.logMessage(data); + }, + + /** + * 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) { + utils.logMessage('concert bidder won bid'); + utils.logMessage(bid); + } + +} + +registerBidder(spec); + +const storage = getStorageManager(); + +/** + * Check or generate a UID for the current user. + */ +function getUid(bidderRequest) { + if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { + return false; + } + + const CONCERT_UID_KEY = 'c_uid'; + + let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY); + + if (!uid) { + uid = utils.generateUUID(); + storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); + } + + return uid; +} + +/** + * Whether the user has opted out of personalization. + */ +function hasOptedOutOfPersonalization() { + const CONCERT_NO_PERSONALIZATION_KEY = 'c_nap'; + + return storage.getDataFromLocalStorage(CONCERT_NO_PERSONALIZATION_KEY) === 'true'; +} + +/** + * Whether the privacy consent strings allow personalization. + * + * @param {BidderRequest} bidderRequest Object which contains any data consent signals + */ +function consentAllowsPpid(bidderRequest) { + /* NOTE: We cannot easily test GDPR consent, without the + * `consent-string` npm module; so will have to rely on that + * happening on the bid-server. */ + return !(bidderRequest.uspConsent === 'string' && + bidderRequest.uspConsent.toUpperCase().substring(0, 2) === '1YY') +} diff --git a/modules/concertBidAdapter.md b/modules/concertBidAdapter.md new file mode 100644 index 00000000000..d8736082e5c --- /dev/null +++ b/modules/concertBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: Concert Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@concert.io +``` + +# Description + +Module that connects to Concert demand sources + +# Test Paramters +``` + var adUnits = [ + { + code: 'desktop_leaderboard_variable', + mediaTypes: { + banner: { + sizes: [[1030, 590]] + } + } + bids: [ + { + bidder: "concert", + params: { + partnerId: 'test_partner', + site: 'site_name', + placementId: 1234567, + slot: 'slot_name', + sizes: [[1030, 590]] + } + } + ] + } + ]; +``` diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 3dcb8da9838..111b6ac10e8 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -2,6 +2,7 @@ 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'; +import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; @@ -10,6 +11,7 @@ const SUPPORTED_MEDIA_TYPES = [BANNER]; export const spec = { code: BIDDER_CODE, + gvlid: 138, aliases: [ BIDDER_CODE_ALIAS ], supportedMediaTypes: SUPPORTED_MEDIA_TYPES, @@ -18,8 +20,6 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { - let digitrust; - let ret = { method: 'POST', url: '', @@ -41,7 +41,8 @@ export const spec = { screensize: getScreenSize(), dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: navigator.language, - ua: navigator.userAgent + ua: navigator.userAgent, + pversion: '$prebid.version$' }); // coppa compliance @@ -69,81 +70,22 @@ export const spec = { utils.deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); } - // Digitrust Support - const bidRequestDigitrust = utils.deepAccess(validBidRequests[0], 'userId.digitrustid.data'); - if (bidRequestDigitrust && (!bidRequestDigitrust.privacy || !bidRequestDigitrust.privacy.optout)) { - digitrust = { - id: bidRequestDigitrust.id, - keyv: bidRequestDigitrust.keyv - } - } - - if (digitrust) { - utils.deepSetValue(data, 'user.ext.digitrust', { - id: digitrust.id, - keyv: digitrust.keyv - }) - } - - if (validBidRequests[0].userId && typeof validBidRequests[0].userId === 'object' && (validBidRequests[0].userId.tdid || validBidRequests[0].userId.pubcid || validBidRequests[0].userId.lipb || validBidRequests[0].userId.id5id || validBidRequests[0].userId.parrableid)) { - utils.deepSetValue(data, 'user.ext.eids', []); - - if (validBidRequests[0].userId.tdid) { - data.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: validBidRequests[0].userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - - if (validBidRequests[0].userId.pubcid) { - data.user.ext.eids.push({ - source: 'pubcommon', - uids: [{ - id: validBidRequests[0].userId.pubcid, - }] - }); - } - - if (validBidRequests[0].userId.id5id) { - data.user.ext.eids.push({ - source: 'id5-sync.com', - uids: [{ - id: validBidRequests[0].userId.id5id, - }] - }); - } - - if (validBidRequests[0].userId.parrableid) { - data.user.ext.eids.push({ - source: 'parrable.com', - uids: [{ - id: validBidRequests[0].userId.parrableid, - }] - }); - } - - if (validBidRequests[0].userId.lipb && validBidRequests[0].userId.lipb.lipbid) { - data.user.ext.eids.push({ - source: 'liveintent.com', - uids: [{ - id: validBidRequests[0].userId.lipb.lipbid - }] - }); - } + // EIDS Support + if (validBidRequests[0].userId) { + utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { const placement = Object.assign({ id: bid.transactionId, divName: bid.bidId, + pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], sizes: bid.mediaTypes.banner.sizes, - adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes) - }, bid.params); + adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes), + bidfloor: getBidFloor(bid), + siteId: bid.params.siteId, + networkId: bid.params.networkId + }); if (placement.networkId && placement.siteId) { data.placements.push(placement); @@ -182,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; @@ -195,6 +138,13 @@ export const spec = { return bidResponses; }, + transformBidParams: function (params, isOpenRtb) { + return utils.convertTypes({ + 'siteId': 'number', + 'networkId': 'number' + }, params); + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; @@ -277,6 +227,22 @@ sizeMap[331] = '320x250'; sizeMap[3301] = '320x267'; sizeMap[2730] = '728x250'; +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + } + + let floor = floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.floorprice || 0; + + return floor; +} + function getSize(sizes) { const result = []; sizes.forEach(function(size) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 53e97006bd1..6a13e73b8a2 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -14,9 +14,12 @@ const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true; +export const allowAuction = { + value: DEFAULT_ALLOW_AUCTION_WO_CONSENT, + definedInConfig: false +} export let userCMP; export let consentTimeout; -export let allowAuction; export let gdprScope; export let staticConsentData; @@ -97,9 +100,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function v2CmpResponseCallback(tcfData, success) { utils.logInfo('Received a response from CMP', tcfData); if (success) { - if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { - cmpSuccess(tcfData, hookConfig); - } else if (tcfData.eventStatus === 'cmpuishown' && tcfData.tcString && tcfData.purposeOneTreatment === true) { + if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { cmpSuccess(tcfData, hookConfig); } } else { @@ -198,29 +199,52 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { let apiName = (cmpVersion === 2) ? '__tcfapi' : '__cmp'; + let callName = `${apiName}Call`; + /* Setup up a __cmp function to do the postMessage and stash the callback. - This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - window[apiName] = function (cmd, arg, callback) { - let callId = Math.random() + ''; - let callName = `${apiName}Call`; - let msg = { - [callName]: { - command: cmd, - parameter: arg, - callId: callId - } - }; - if (cmpVersion !== 1) msg[callName].version = cmpVersion; + This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ + if (cmpVersion === 2) { + window[apiName] = function (cmd, cmpVersion, callback, arg) { + let callId = Math.random() + ''; + let msg = { + [callName]: { + command: cmd, + version: cmpVersion, + parameter: arg, + callId: callId + } + }; - cmpCallbacks[callId] = callback; - cmpFrame.postMessage(msg, '*'); - } + cmpCallbacks[callId] = callback; + cmpFrame.postMessage(msg, '*'); + } - /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); - // call CMP - window[apiName](commandName, null, moduleCallback); + // call CMP + window[apiName](commandName, cmpVersion, moduleCallback); + } else { + window[apiName] = function (cmd, arg, callback) { + let callId = Math.random() + ''; + let msg = { + [callName]: { + command: cmd, + parameter: arg, + callId: callId + } + }; + + cmpCallbacks[callId] = callback; + cmpFrame.postMessage(msg, '*'); + } + + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); + + // call CMP + window[apiName](commandName, undefined, moduleCallback); + } function readPostMessageResponse(event) { let cmpDataPkgName = `${apiName}Return`; @@ -322,6 +346,13 @@ function processCmpData(consentObject, hookConfig) { // determine which set of checks to run based on cmpVersion let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; + // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. + if (allowAuction.definedInConfig && cmpVersion === 2) { + utils.logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); + } else if (!allowAuction.definedInConfig && cmpVersion === 1) { + utils.logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + } + if (utils.isFn(checkFn)) { if (checkFn(consentObject)) { cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); @@ -352,14 +383,14 @@ function cmpFailed(errMsg, hookConfig, extraArgs) { clearTimeout(hookConfig.timer); // still set the consentData to undefined when there is a problem as per config options - if (allowAuction) { + if (allowAuction.value && cmpVersion === 1) { storeConsentData(undefined); } exitModule(errMsg, hookConfig, extraArgs); } /** - * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction + * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeConsentData(cmpConsentObject) { @@ -375,6 +406,9 @@ function storeConsentData(cmpConsentObject) { vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope }; + if (cmpConsentObject && cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + consentData.addtlConsent = cmpConsentObject.addtlConsent; + }; } consentData.apiVersion = cmpVersion; gdprDataHandler.setConsentData(consentData); @@ -406,8 +440,8 @@ function exitModule(errMsg, hookConfig, extraArgs) { let nextFn = hookConfig.nextFn; if (errMsg) { - if (allowAuction) { - utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); + if (allowAuction.value && cmpVersion === 1) { + utils.logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); nextFn.apply(context, args); } else { utils.logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); @@ -440,7 +474,7 @@ export function resetConsentData() { export function setConsentConfig(config) { // if `config.gdpr` or `config.usp` exist, assume new config format. // else for backward compatability, just use `config` - config = config.gdpr || config.usp ? config.gdpr : config; + config = config && (config.gdpr || config.usp ? config.gdpr : config); if (!config || typeof config !== 'object') { utils.logWarn('consentManagement config not defined, exiting consent manager'); return; @@ -460,10 +494,8 @@ export function setConsentConfig(config) { } if (typeof config.allowAuctionWithoutConsent === 'boolean') { - allowAuction = config.allowAuctionWithoutConsent; - } else { - allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT; - utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + allowAuction.value = config.allowAuctionWithoutConsent; + allowAuction.definedInConfig = true; } // if true, then gdprApplies should be set to true diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 1a5879a40ff..cba9c2758d0 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -44,6 +44,35 @@ function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ function lookupUspConsent(uspSuccess, uspError, hookConfig) { + function findUsp() { + let f = window; + let uspapiFrame; + let uspapiFunction; + + while (!uspapiFrame) { + try { + if (typeof f.__uspapi === 'function') { + uspapiFunction = f.__uspapi; + uspapiFrame = f; + break; + } + } catch (e) {} + + try { + if (f.frames['__uspapiLocator']) { + uspapiFrame = f; + break; + } + } catch (e) {} + if (f === window.top) break; + f = f.parent; + } + return { + uspapiFrame, + uspapiFunction, + }; + } + function handleUspApiResponseCallbacks() { const uspResponse = {}; @@ -61,41 +90,43 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { uspResponse.usPrivacy = consentResponse.uspString; } afterEach(); - } + }, }; } let callbackHandler = handleUspApiResponseCallbacks(); let uspapiCallbacks = {}; + let { uspapiFrame, uspapiFunction } = findUsp(); + + if (!uspapiFrame) { + return uspError('USP CMP not found.', hookConfig); + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) // the following code also determines where the USPAPI is located and uses the proper workflow to communicate with it: - // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. This works in friendly or cross domain iframes + // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. + // - 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 - // - try to call the __uspapi() function directly, otherwise use the postMessage() api - // find the CMP frame/window - - try { - // try to call __uspapi directly - window.__uspapi('getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback); - } catch (e) { - // must not have been accessible, try using postMessage() api - let f = window; - let uspapiFrame; - while (!uspapiFrame) { - try { - if (f.frames['__uspapiLocator']) uspapiFrame = f; - } catch (e) { } - if (f === window.top) break; - f = f.parent; - } - if (!uspapiFrame) { - return uspError('USP CMP not found.', hookConfig); - } - callUspApiWhileInIframe('getUSPData', uspapiFrame, callbackHandler.consentDataCallback); + if (utils.isFn(uspapiFunction)) { + utils.logInfo('Detected USP CMP is directly accessible, calling it now...'); + uspapiFunction( + 'getUSPData', + USPAPI_VERSION, + callbackHandler.consentDataCallback + ); + } else { + utils.logInfo( + 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' + ); + callUspApiWhileInIframe( + 'getUSPData', + uspapiFrame, + callbackHandler.consentDataCallback + ); } function callUspApiWhileInIframe(commandName, uspapiFrame, moduleCallback) { @@ -107,19 +138,19 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { __uspapiCall: { command: cmd, version: ver, - callId: callId - } + callId: callId, + }, }; uspapiCallbacks[callId] = callback; uspapiFrame.postMessage(msg, '*'); - } + }; /** when we get the return message, call the stashed callback */ window.addEventListener('message', readPostMessageResponse, false); // call uspapi - window.__uspapi(commandName, USPAPI_VERSION, uspapiCallback); + window.__uspapi(commandName, USPAPI_VERSION, moduleCallback); function readPostMessageResponse(event) { const res = event && event.data && event.data.__uspapiReturn; @@ -130,11 +161,6 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { } } } - - function uspapiCallback(consentObject, success) { - window.removeEventListener('message', readPostMessageResponse, false); - moduleCallback(consentObject, success); - } } } @@ -158,11 +184,6 @@ export function requestBidsHook(fn, reqBidsConfigObj) { timer: null }; - // in case we already have consent (eg during bid refresh) - if (consentData) { - return exitModule(null, hookConfig); - } - if (!uspCallMap[consentAPI]) { utils.logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); @@ -274,7 +295,7 @@ export function resetConsentData() { * @param {object} config required; consentManagementUSP module config settings; usp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { - config = config.usp; + config = config && config.usp; if (!config || typeof config !== 'object') { utils.logWarn('consentManagement.usp config not defined, exiting usp consent manager'); return; diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 8eb56f7d0c2..92e2192b925 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -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; diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js new file mode 100644 index 00000000000..de4cf8df933 --- /dev/null +++ b/modules/contentexchangeBidAdapter.js @@ -0,0 +1,209 @@ +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 = '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 (!utils.isFn(bid.getFloor)) { + return utils.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) { + utils.logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + utils.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/convergeBidAdapter.md b/modules/convergeBidAdapter.md deleted file mode 100644 index ab916a8b3b6..00000000000 --- a/modules/convergeBidAdapter.md +++ /dev/null @@ -1,57 +0,0 @@ -# Overview - -Module Name: Converge Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support@converge-digital.com - -# Description - -Module that connects to Converge demand source to fetch bids. -Converge Bid Adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "converge", - params: { - uid: '59', - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "converge", - params: { - uid: 1, - priceType: 'gross', - keywords: { - brandsafety: ['disaster'], - topic: ['stress', 'fear'] - } - } - } - ] - },{ - code: 'test-div', - sizes: [[640, 360]], - mediaTypes: { video: {} }, - bids: [ - { - bidder: "converge", - params: { - uid: 60 - } - } - ] - } - ]; -``` diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 3b3d04dc498..a1ca094273b 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,7 +1,7 @@ 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'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; const GVLID = 24; export const storage = getStorageManager(GVLID); @@ -33,10 +33,11 @@ export const spec = { } if (isVideoRequest(bid)) { - if (!bid.params.mimes) { + const mimes = bid.params.mimes || utils.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))) { + } else if (!utils.isArray(mimes) || !mimes.every(s => utils.isStr(s))) { utils.logWarn(BIDDER_CODE + ': mimes must be an array of strings'); return false; } @@ -61,7 +62,7 @@ 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; @@ -90,7 +91,7 @@ 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'); @@ -205,6 +206,10 @@ export const spec = { ttl: 300, netRevenue: true }; + bid.meta = {}; + if (conversantBid.adomain && conversantBid.adomain.length > 0) { + bid.meta.advertiserDomains = conversantBid.adomain; + } if (request.video) { if (responseAd.charAt(0) === '<') { @@ -243,6 +248,48 @@ export const spec = { '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 = utils.parseUrl(endpoint); + utils.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: utils.buildUrl(urlInfo)}; + }) + .reduce((x, y) => x.concat(y), []); + }) + .reduce((x, y) => x.concat(y), []); + syncs.push(...pixels); + } + return syncs; } }; @@ -331,7 +378,6 @@ function collectEids(bidRequests) { 'criteo.com': 1, 'id5-sync.com': 1, 'parrable.com': 1, - 'digitru.st': 1, 'liveintent.com': 1 }; request.userIdAsEids.forEach(function(eid) { @@ -374,4 +420,28 @@ function readStoredValue(key) { 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 = utils.getBidIdParameter('bidfloor', bid.params); + + if (!floor && utils.isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (utils.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 index b416c00c2d0..14c0d43add7 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -13,6 +13,12 @@ const ENDPOINT_PRODUCTION = 'https://server.cpmstar.com/view.aspx'; const DEFAULT_TTL = 300; const DEFAULT_CURRENCY = 'USD'; +function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -47,13 +53,29 @@ export const spec = { 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) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + encodeURIComponent(referer); + if (bidRequest.schain && bidRequest.schain.nodes) { + var schain = bidRequest.schain; + var schainString = ''; + schainString += schain.ver + ',' + schain.complete; + for (var i2 = 0; i2 < schain.nodes.length; i2++) { + var node = schain.nodes[i2]; + schainString += '!' + + fixedEncodeURIComponent(node.asi || '') + ',' + + fixedEncodeURIComponent(node.sid || '') + ',' + + fixedEncodeURIComponent(node.hp || '') + ',' + + fixedEncodeURIComponent(node.rid || '') + ',' + + fixedEncodeURIComponent(node.name || '') + ',' + + fixedEncodeURIComponent(node.domain || ''); + } + url += '&schain=' + schainString + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString != null) { url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString; @@ -114,6 +136,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')) { @@ -138,6 +163,21 @@ export const spec = { } return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + if (serverResponses.length == 0 || !serverResponses[0].body) return syncs; + var usersyncs = serverResponses[0].body[0].syncs; + if (!usersyncs || usersyncs.length < 0) return syncs; + for (var i = 0; i < usersyncs.length; i++) { + var us = usersyncs[i]; + if ((us.type === 'image' && syncOptions.pixelEnabled) || (us.type == 'iframe' && syncOptions.iframeEnabled)) { + syncs.push(us); + } + } + return syncs; } + }; registerBidder(spec); diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 3838f5dee59..0124f96a107 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -7,7 +7,7 @@ import includes from 'core-js-pure/features/array/includes.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'craft'; -const URL = 'https://gacraft.jp/prebid-v3'; +const URL_BASE = 'https://gacraft.jp/prebid-v3'; const TTL = 360; const storage = getStorageManager(); @@ -143,10 +143,11 @@ function formatRequest(payload, bidderRequest) { withCredentials: false }; } + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: URL, + url: `${URL_BASE}/${payload.tags[0].sitekey}`, data: payloadString, bidderRequest, options diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 3dabe911884..aa7308d1f4c 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -4,11 +4,11 @@ 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 = 32; +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 = 105; +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='; @@ -28,7 +37,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], - /** + /** f * @param {object} bid * @return {boolean} */ @@ -56,33 +65,33 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; Object.assign(bidderRequest, { - publisherExt: config.getConfig('fpd.context'), - userExt: config.getConfig('fpd.user'), + publisherExt: fpd.context, + userExt: fpd.user, ceh: config.getConfig('criteo.ceh') }); // 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); } if (publisherTagAvailable()) { // eslint-disable-next-line no-undef const adapter = new Criteo.PubTag.Adapters.Prebid(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$'); - const enableSendAllBids = config.getConfig('enableSendAllBids'); - if (adapter.setEnableSendAllBids && typeof adapter.setEnableSendAllBids === 'function' && typeof enableSendAllBids === 'boolean') { - adapter.setEnableSendAllBids(enableSendAllBids); - } url = adapter.buildCdbUrl(); data = adapter.buildCdbRequest(); } else { @@ -125,16 +134,17 @@ export const spec = { 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); - } else if (config.getConfig('enableSendAllBids') === true) { - return; } else { bid.native = createPrebidNativeAd(slot.native); bid.mediaType = NATIVE; @@ -253,12 +263,14 @@ function buildCdbUrl(context) { function checkNativeSendId(bidRequest) { return !(bidRequest.nativeParams && - ((bidRequest.nativeParams.image && bidRequest.nativeParams.image.sendId !== true) || - (bidRequest.nativeParams.icon && bidRequest.nativeParams.icon.sendId !== true) || - (bidRequest.nativeParams.clickUrl && bidRequest.nativeParams.clickUrl.sendId !== true) || - (bidRequest.nativeParams.displayUrl && bidRequest.nativeParams.displayUrl.sendId !== true) || - (bidRequest.nativeParams.privacyLink && bidRequest.nativeParams.privacyLink.sendId !== true) || - (bidRequest.nativeParams.privacyIcon && bidRequest.nativeParams.privacyIcon.sendId !== true))); + ( + (bidRequest.nativeParams.image && ((bidRequest.nativeParams.image.sendId !== true || bidRequest.nativeParams.image.sendTargetingKeys === true))) || + (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || + (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) + )); } /** @@ -284,8 +296,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (bidRequest.fpd && bidRequest.fpd.context) { - slot.ext = bidRequest.fpd.context; + if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); @@ -308,14 +320,21 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { 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; } @@ -370,38 +389,27 @@ function parseNativeSize(size) { } function hasVideoMediaType(bidRequest) { - if (utils.deepAccess(bidRequest, 'params.video') === undefined) { - return false; - } return utils.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) { + if (utils.deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && utils.deepAccess(bidRequest, 'params.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) { - isValid = false; - utils.logError('Criteo Bid Adapter: params.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; } } @@ -416,6 +424,7 @@ function hasValidVideoMediaType(bidRequest) { */ function createPrebidNativeAd(payload) { return { + sendTargetingKeys: false, // no key is added to KV by default title: payload.products[0].title, body: payload.products[0].description, sponsoredBy: payload.advertiser.description, @@ -456,7 +465,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) { + utils.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: '; @@ -487,6 +518,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 e4c441c758d..6a165978f3b 100644 --- a/modules/criteoBidAdapter.md +++ b/modules/criteoBidAdapter.md @@ -2,7 +2,7 @@ Module Name: Criteo Bidder Adapter Module Type: Bidder Adapter -Maintainer: pi-direct@criteo.com +Maintainer: prebid@criteo.com # Description @@ -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 c44f0c843ae..6a028a6cc25 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -6,30 +6,24 @@ */ import * as utils from '../src/utils.js' -import * as ajax from '../src/ajax.js' -import { getRefererInfo } from '../src/refererDetection.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'; -export const storage = getStorageManager(); +const gvlid = 91; +const bidderCode = 'criteo'; +export const storage = getStorageManager(gvlid, bidderCode); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; -const cookieWriteableKey = 'cto_test_cookie'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; const pastDateString = new Date(0).toString(); const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); -function areCookiesWriteable() { - storage.setCookie(cookieWriteableKey, '1'); - const canWrite = storage.getCookie(cookieWriteableKey) === '1'; - storage.setCookie(cookieWriteableKey, '', pastDateString); - return canWrite; -} - function extractProtocolHost (url, returnOnlyHost = false) { - const parsedUrl = utils.parseUrl(url) + const parsedUrl = utils.parseUrl(url, {noDecodeWholeURL: true}) return returnOnlyHost ? `${parsedUrl.hostname}` : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; @@ -58,20 +52,22 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent, gdprString) { +function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { const url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + - `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + + `${isLocalStorageWritable ? '&lsw=1' : ''}`; return url; } -function callCriteoUserSync(parsedCriteoData, gdprString) { - const cw = areCookiesWriteable(); +function callCriteoUserSync(parsedCriteoData, gdprString, callback) { + const cw = storage.cookiesAreEnabled(); + const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().referer); const domain = extractProtocolHost(document.location.href, true); const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase @@ -81,28 +77,37 @@ function callCriteoUserSync(parsedCriteoData, gdprString) { domain, parsedCriteoData.bundle, cw, + lsw, isPublishertagPresent, 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)); } else if (jsonResponse.bundle) { saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); } + + if (jsonResponse.bidId) { + saveOnAllStorages(bididStorageKey, jsonResponse.bidId); + const criteoId = { criteoId: jsonResponse.bidId }; + callback(criteoId); + } else { + deleteFromAllStorages(bididStorageKey); + callback(); + } + }, + error: error => { + utils.logError(`criteoIdSystem: unable to sync user id`, error); + callback(); } - ); + }; + + ajax(url, callbacks, undefined, { method: 'GET', contentType: 'application/json', withCredentials: true }); } /** @type {Submodule} */ @@ -111,7 +116,8 @@ export const criteoIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'criteo', + name: bidderCode, + gvlid: gvlid, /** * decode the stored id value for passing to bid requests * @function @@ -123,18 +129,22 @@ export const criteoIdSubmodule = { /** * get the Criteo Id from local storages and initiate a new user sync * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(configParams, consentData) { + getId(config, consentData) { const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; 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/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js deleted file mode 100644 index 1018417300a..00000000000 --- a/modules/dailyhuntBidAdapter.js +++ /dev/null @@ -1,395 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import * as mediaTypes from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import find from 'core-js-pure/features/array/find.js'; -import { OUTSTREAM, INSTREAM } from '../src/video.js'; - -const BIDDER_CODE = 'dailyhunt'; -const BIDDER_ALIAS = 'dh'; -const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE, mediaTypes.VIDEO]; - -const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner='; -const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner='; - -const ORTB_NATIVE_TYPE_MAPPING = { - img: { - '3': 'image', - '1': 'icon' - }, - data: { - '1': 'sponsoredBy', - '2': 'body', - '3': 'rating', - '4': 'likes', - '5': 'downloads', - '6': 'price', - '7': 'salePrice', - '8': 'phone', - '9': 'address', - '10': 'body2', - '11': 'displayUrl', - '12': 'cta' - } -} - -const ORTB_NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 1, - type: 1, - name: 'img' - }, - image: { - id: 2, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 3, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 5, - type: 12, - name: 'data' - }, - body2: { - id: 4, - name: 'data', - type: 10 - }, -}; - -// Encode URI. -const _encodeURIComponent = function (a) { - let b = window.encodeURIComponent(a); - b = b.replace(/'/g, '%27'); - return b; -} - -// Extract key from collections. -const extractKeyInfo = (collection, key) => { - for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i].params, key); - if (result) { - return result; - } - } - return undefined -} - -// Flattern Array. -const flatten = (arr) => { - return [].concat(...arr); -} - -const createOrtbRequest = (validBidRequests, bidderRequest) => { - let device = createOrtbDeviceObj(validBidRequests); - let user = createOrtbUserObj(validBidRequests) - let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer) - return { - id: bidderRequest.auctionId, - imp: [], - site, - device, - user, - }; -} - -const createOrtbDeviceObj = (validBidRequests) => { - let device = { ...extractKeyInfo(validBidRequests, `device`) }; - device.ua = navigator.userAgent; - return device; -} - -const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `user`) }) - -const createOrtbSiteObj = (validBidRequests, page) => { - let site = { ...extractKeyInfo(validBidRequests, `site`), page }; - let publisher = createOrtbPublisherObj(validBidRequests); - if (publisher) { - site.publisher = publisher - } - return site -} - -const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) }) - -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 imp = { - id: bid.bidId, - bidfloor: params.bidfloor ? params.bidfloor : 0, - ext: { - dailyhunt: { - placement_id: params.placement_id, - publisher_id: params.publisher_id, - partner: params.partner_name - } - } - }; - - // Test Mode Campaign. - if (testMode) { - imp.ext.test_mode = testMode; - } - - if (bannerObj) { - imp.banner = { - ...createOrtbImpBannerObj(bid, bannerObj) - } - } else if (nativeObj) { - imp.native = { - ...createOrtbImpNativeObj(bid, nativeObj) - } - } else if (videoObj) { - imp.video = { - ...createOrtbImpVideoObj(bid, videoObj) - } - } - return imp; -} - -const createOrtbImpBannerObj = (bid, bannerObj) => { - let format = []; - bannerObj.sizes.forEach(size => format.push({ w: size[0], h: size[1] })) - - return { - id: 'banner-' + bid.bidId, - format - } -} - -const createOrtbImpNativeObj = (bid, nativeObj) => { - const assets = utils._map(bid.nativeParams, (bidParams, key) => { - const props = ORTB_NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - let h = 0; - let w = 0; - - asset.id = props.id; - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len ? bidParams.len : 20, - type: props.type, - w, - h - }; - - return asset; - } - }).filter(Boolean); - let request = { - assets, - ver: '1,0' - } - return { request: JSON.stringify(request) }; -} - -const createOrtbImpVideoObj = (bid, videoObj) => { - let obj = {}; - let params = bid.params - if (!utils.isEmpty(bid.params.video)) { - obj = { - ...params.video, - } - } else { - obj = { - mimes: ['video/mp4'], - }; - } - obj.ext = { - ...videoObj, - } - return obj; -} - -const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({ - method: 'POST', - url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name, - data: JSON.stringify(ortbRequest), - options: { - contentType: 'application/json', - withCredentials: true - }, - bids: validBidRequests -}) - -const createPrebidBannerBid = (bid, bidResponse) => ({ - requestId: bid.bidId, - cpm: bidResponse.price.toFixed(2), - creativeId: bidResponse.crid, - width: bidResponse.w, - height: bidResponse.h, - ttl: 360, - netRevenue: bid.netRevenue === 'net', - currency: 'USD', - ad: bidResponse.adm, - mediaType: 'banner', - winUrl: bidResponse.nurl -}) - -const createPrebidNativeBid = (bid, bidResponse) => ({ - requestId: bid.bidId, - cpm: bidResponse.price.toFixed(2), - creativeId: bidResponse.crid, - currency: 'USD', - ttl: 360, - netRevenue: bid.netRevenue === 'net', - native: parseNative(bidResponse), - mediaType: 'native', - winUrl: bidResponse.nurl, - width: bidResponse.w, - height: bidResponse.h, -}) - -const parseNative = (bid) => { - let adm = JSON.parse(bid.adm) - const { assets, link, imptrackers, jstracker } = adm.native; - const result = { - clickUrl: _encodeURIComponent(link.url), - clickTrackers: link.clicktrackers || [], - impressionTrackers: imptrackers || [], - javascriptTrackers: jstracker ? [ jstracker ] : [] - }; - assets.forEach(asset => { - if (!utils.isEmpty(asset.title)) { - result.title = asset.title.text - } else if (!utils.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)) { - result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value - } - }); - - return result; -} - -const createPrebidVideoBid = (bid, bidResponse) => { - let videoBid = { - requestId: bid.bidId, - cpm: bidResponse.price.toFixed(2), - creativeId: bidResponse.crid, - width: bidResponse.w, - height: bidResponse.h, - ttl: 360, - netRevenue: bid.netRevenue === 'net', - currency: 'USD', - mediaType: 'video', - winUrl: bidResponse.nurl, - }; - - let videoContext = bid.mediaTypes.video.context; - switch (videoContext) { - case OUTSTREAM: - videoBid.vastXml = bidResponse.adm; - break; - case INSTREAM: - videoBid.videoCacheKey = bidResponse.ext.bidder.cacheKey; - videoBid.vastUrl = bidResponse.ext.bidder.vastUrl; - break; - } - return videoBid; -} - -const getQueryVariable = (variable) => { - let query = window.location.search.substring(1); - let vars = query.split('&'); - for (var i = 0; i < vars.length; i++) { - let pair = vars[i].split('='); - if (decodeURIComponent(pair[0]) == variable) { - return decodeURIComponent(pair[1]); - } - } - return false; -} - -export const spec = { - code: BIDDER_CODE, - - aliases: [BIDDER_ALIAS], - - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - - isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name, - - buildRequests: function (validBidRequests, bidderRequest) { - let serverRequests = []; - - // ORTB Request. - let ortbReq = createOrtbRequest(validBidRequests, bidderRequest); - - validBidRequests.forEach((bid) => { - let imp = createOrtbImpObj(bid) - ortbReq.imp.push(imp); - }); - - serverRequests.push({ ...createServerRequest(ortbReq, validBidRequests, getQueryVariable('dh_test')) }); - - return serverRequests; - }, - - interpretResponse: function (serverResponse, request) { - const { seatbid } = serverResponse.body; - let bids = request.bids; - let prebidResponse = []; - - let seatBids = seatbid[0].bid; - - seatBids.forEach(ortbResponseBid => { - let bidId = ortbResponseBid.impid; - let actualBid = find(bids, (bid) => bid.bidId === bidId); - let bidMediaType = ortbResponseBid.ext.prebid.type - switch (bidMediaType) { - case mediaTypes.BANNER: - prebidResponse.push(createPrebidBannerBid(actualBid, ortbResponseBid)); - break; - case mediaTypes.NATIVE: - prebidResponse.push(createPrebidNativeBid(actualBid, ortbResponseBid)); - break; - case mediaTypes.VIDEO: - prebidResponse.push(createPrebidVideoBid(actualBid, ortbResponseBid)); - break; - } - }) - return prebidResponse; - }, - - onBidWon: function(bid) { - ajax(bid.winUrl, null, null, { - method: 'GET' - }) - } -} - -registerBidder(spec); diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index b00a3eae659..bfbe7a16fc6 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,330 +1,627 @@ -import * as utils 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 * as utils from '../src/utils.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.datablocks.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 = utils.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 (utils.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: utils.deepAccess(bidRequest, `ortb2Imp`) || {}, + floor: {} } + // CHECK FOR FLOORS + if (typeof bidRequest.getFloor === 'function') { + imp.floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + } + + // BUILD THE SIZES if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = bidRequest.mediaTypes.banner.sizes; - if (sizes.length == 1) { + let sizes = utils.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 (utils.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.datablocks.net'; + + // RETURN WITH THE REQUEST AND PAYLOAD + return { + method: 'POST', + url: `https://${sourceId}.${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 (!utils.isEmpty(title)) { + result.title = title.text + } else if (!utils.isEmpty(img)) { + result[key] = { + url: img.url, + height: img.h, + width: img.w + } + } else if (!utils.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 = utils.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 = utils.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/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index c4dc23cf912..25c6ee9b25b 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -49,6 +49,8 @@ export const spec = { utils.deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } + injectEids(openRtbBidRequest, validBidRequests); + return { method: 'POST', url: BIDDER_ENDPOINT, @@ -86,6 +88,9 @@ function formatResponse(bid) { width: bid && bid.w ? bid.w : 0, height: bid && bid.h ? bid.h : 0, ad: bid && bid.adm ? bid.adm : '', + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, creativeId: bid && bid.crid ? bid.crid : undefined, netRevenue: false, currency: bid && bid.cur ? bid.cur : 'USD', @@ -128,6 +133,14 @@ 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); + utils.deepSetValue(openRtbBidRequest, 'user.ext.eids', bidUserIdAsEids); + } +} + function buildBanner(bid) { if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js new file mode 100644 index 00000000000..375c8c07ed1 --- /dev/null +++ b/modules/deepintentDpesIdSystem.js @@ -0,0 +1,45 @@ +/** + * This module adds DPES to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/deepintentDpesSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'deepintentId'; +export const storage = getStorageManager(null, MODULE_NAME); + +/** @type {Submodule} */ +export const deepintentDpesSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{deepintentId:Object}} + */ + decode(value, config) { + return value ? { 'deepintentId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData + * @param {Object} cacheIdObj - existing id, if any + * @return {{id: string | undefined} | undefined} + */ + getId(config, consentData, cacheIdObj) { + return cacheIdObj; + } + +}; + +submodule('userId', deepintentDpesSubmodule); diff --git a/modules/deepintentDpesIdSystem.md b/modules/deepintentDpesIdSystem.md new file mode 100644 index 00000000000..2af0fe7446e --- /dev/null +++ b/modules/deepintentDpesIdSystem.md @@ -0,0 +1,43 @@ +# Deepintent DPES ID + +The Deepintent Id is a shared, healthcare identifier which helps publisher in absence of the 3rd Party cookie matching. This lets publishers set and bid with healthcare identity . Deepintent lets users protect their privacy through advertising value chain, where Healthcare identity when setting the identity takes in consideration of users choices, as well as when passing identity on the cookie itself privacy consent strings are checked. The healthcare identity when set is not stored on Deepintent's servers but is stored on users browsers itself. User can still opt out of the ads by https://option.deepintent.com/adchoices. + +## Deepintent DPES ID Registration + +The Deepintent DPES ID is free to use, but requires a simple registration with Deepintent. Please reach to prebid@deepintent.com to get started. +Once publisher registers with deepintents platform for healthcare identity Deepintent provides the Tag code to be placed on the page, this tag code works to capture and store information as per publishers and users agreement. DPES User ID module uses this stored id and passes it on the deepintent prebid adapter. + + +## Deepintent DPES ID Configuration + +First, make sure to add the Deepintent submodule to your Prebid.js package with: + +``` +gulp build --modules=deepintentDpesIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 90 // storage lasts for 90 days, optional if storage type is html5 + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module: `"deepintentId"` | `"deepintentId"` | +| storage | Required | Object | Storage settings for how the User Id module will cache the Deepintent ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. Deepintent`"html5"` or `"cookie"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. | `"_dpes_id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Deepintent recommends `90`. | `90` | \ No newline at end of file diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 71a8471d554..79cb03ec001 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,6 +8,9 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; +import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; /** * @typedef {Object} DfpVideoParams @@ -42,7 +45,7 @@ import { auctionManager } from '../src/auctionManager.js'; const defaultParamConstants = { env: 'vp', gdfp_req: 1, - output: 'xml_vast3', + output: 'vast', unviewed_position_start: 1, }; @@ -82,7 +85,7 @@ export function buildDfpVideoUrl(options) { const derivedParams = { correlator: Date.now(), - sz: parseSizesInput(adUnit.sizes).join('|'), + sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), url: encodeURIComponent(location.href), }; const encodedCustomParams = getCustParams(bid, options); @@ -98,6 +101,16 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + return buildUrl({ protocol: 'https', host: 'securepubads.g.doubleclick.net', @@ -110,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 @@ -181,6 +194,16 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { { cust_params: encodedCustomParams } ); + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + const masterTag = buildUrl({ protocol: 'https', host: 'securepubads.g.doubleclick.net', @@ -245,17 +268,20 @@ function getCustParams(bid, options) { allTargetingData = (allTargeting) ? allTargeting[adUnit.code] : {}; } - const optCustParams = deepAccess(options, 'params.cust_params'); - let customParams = Object.assign({}, + 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, - optCustParams, ); - return encodeURIComponent(formatQS(customParams)); + events.emit(CONSTANTS.EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); + + // merge the prebid + publisher targeting sets + const publisherTargetingSet = deepAccess(options, 'params.cust_params'); + const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet); + return encodeURIComponent(formatQS(targetingSet)); } registerVideoSupport('dfp', { diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js new file mode 100644 index 00000000000..58cec36a6b9 --- /dev/null +++ b/modules/dgkeywordRtdProvider.js @@ -0,0 +1,134 @@ +/** + * 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 * as utils 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; + utils.logMessage('[dgkeyword sub module]', adUnits, timeout); + let setKeywordTargetBidders = getTargetBidderOfDgKeywords(adUnits); + if (setKeywordTargetBidders.length <= 0) { + utils.logMessage('[dgkeyword sub module] no dgkeyword targets.'); + callback(); + } else { + utils.logMessage('[dgkeyword sub module] dgkeyword targets:', setKeywordTargetBidders); + utils.logMessage('[dgkeyword sub module] get targets from profile api start.'); + ajax(url, { + success: function(response) { + const res = JSON.parse(response); + if (!isFinish) { + utils.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 = {}; + utils.deepSetValue(addOrtb2, 'site.keywords', keywords); + utils.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 + utils.logError('[dgkeyword sub module] profile api access error.', errorStatus); + callback(); + } + }, null, { + withCredentials: true, + contentType: 'application/json', + }); + setTimeout(function () { + if (!isFinish) { + // profile api timeout + utils.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/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js deleted file mode 100644 index d8aa8be9376..00000000000 --- a/modules/digiTrustIdSystem.js +++ /dev/null @@ -1,460 +0,0 @@ -/** - * This module adds DigiTrust ID support to the User ID module - * The {@link module:modules/userId} module is required - * If the full DigiTrust Id library is included the standard functions - * will be invoked to obtain the user's DigiTrust Id. - * When the full library is not included this will fall back to the - * DigiTrust Identity API and generate a mock DigiTrust object. - * @module modules/digiTrustIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils.js' -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const DT_VENDOR_ID = 64; // cmp gvlVendorId -const storage = getStorageManager(DT_VENDOR_ID); - -var fallbackTimeout = 1550; // timeout value that allows userId system to execute first -var fallbackTimer = 0; // timer Id for fallback init so we don't double call - -/** - * Checks to see if the DigiTrust framework is initialized. - * @function - */ -function isInitialized() { - if (window.DigiTrust == null) { - return false; - } - // eslint-disable-next-line no-undef - return DigiTrust.isClient; // this is set to true after init -} - -/** - * Tests for presence of the DigiTrust object - * */ -function isPresent() { - return (window.DigiTrust != null); -} - -var noop = function () { -}; - -const MAX_RETRIES = 2; -const DT_ID_SVC = 'https://prebid.digitru.st/id/v1'; - -var isFunc = function (fn) { - return typeof (fn) === 'function'; -} - -var _savedId = null; // closure variable for storing Id to avoid additional requests - -function callApi(options) { - var ajaxOptions = { - method: 'GET', - withCredentials: true - }; - - ajax( - DT_ID_SVC, - { - success: options.success, - error: options.fail - }, - null, - ajaxOptions - ); -} - -/** - * Encode the Id per DigiTrust lib - * @param {any} id - */ -function encId(id) { - try { - if (typeof (id) !== 'string') { - id = JSON.stringify(id); - } - return btoa(id); - } catch (ex) { - return id; - } -} - -/** - * Writes the Identity into the expected DigiTrust cookie - * @param {any} id - */ -function writeDigiId(id) { - var key = 'DigiTrust.v1.identity'; - var date = new Date(); - date.setTime(date.getTime() + 604800000); - storage.setCookie(key, encId(id), date.toUTCString(), 'none'); -} - -/** - * Tests to see if the current browser is FireFox - */ -function isFirefoxBrowser(ua) { - ua = ua || navigator.userAgent; - ua = ua.toLowerCase(); - if (ua.indexOf('firefox') !== -1) { - return true; - } - return false; -} - -/** - * Test to see if the user has a browser that is disallowed for making AJAX - * requests due to the behavior not supported DigiTrust ID Cookie. - */ -function isDisallowedBrowserForApiCall() { - if (utils.isSafariBrowser()) { - return true; - } else if (isFirefoxBrowser()) { - return true; - } - return false; -} - -/** - * Set up a DigiTrust facade object to mimic the API - * - */ -function initDigitrustFacade(config) { - clearTimeout(fallbackTimer); - fallbackTimer = 0; - - var facade = { - isClient: true, - isMock: true, - _internals: { - callCount: 0, - initCallback: null - }, - getUser: function (obj, callback) { - var isAsync = !!isFunc(callback); - var cb = isAsync ? callback : noop; - var errResp = { success: false }; - var inter = facade._internals; - inter.callCount++; - - // wrap the initializer callback, if present - var checkAndCallInitializeCb = function (idResponse) { - if (inter.callCount <= 1 && isFunc(inter.initCallback)) { - try { - inter.initCallback(idResponse); - } catch (ex) { - utils.logError('Exception in passed DigiTrust init callback', ex); - } - } - } - - if (!isMemberIdValid(obj.member)) { - if (!isAsync) { - return errResp - } else { - cb(errResp); - return; - } - } - - if (_savedId != null) { - if (isAsync) { - checkAndCallInitializeCb(_savedId); - // cb(_savedId); - return; - } else { - return _savedId; - } - } - - var opts = { - success: function (respText, result) { - var idResult = { - success: true - } - try { - idResult.identity = JSON.parse(respText); - _savedId = idResult; // Save result to the cache variable - writeDigiId(respText); - } catch (ex) { - idResult.success = false; - delete idResult.identity; - } - checkAndCallInitializeCb(idResult); - }, - fail: function (statusErr, result) { - utils.logError('DigiTrustId API error: ' + statusErr); - } - } - - // check gdpr vendor here. Full DigiTrust library has vendor check built in - gdprConsent.hasConsent(null, function (hasConsent) { - if (hasConsent) { - if (isDisallowedBrowserForApiCall()) { - let resultObj = { - success: false, - err: 'Your browser does not support DigiTrust Identity' - } - checkAndCallInitializeCb(resultObj); - return; - } - callApi(opts); - } - }) - - if (!isAsync) { - return errResp; // even if it will be successful later, without a callback we report a "failure in this moment" - } - } - } - - if (config && isFunc(config.callback)) { - facade._internals.initCallback = config.callback; - } - - if (window && window.DigiTrust == null) { - window.DigiTrust = facade; - } -} - -/** - * Tests to see if a member ID is valid within facade - * @param {any} memberId - */ -var isMemberIdValid = function (memberId) { - if (memberId && memberId.length > 0) { - return true; - } else { - utils.logError('[DigiTrust Prebid Client Error] Missing member ID, add the member ID to the function call options'); - return false; - } -}; - -/** - * DigiTrust consent handler for GDPR and __cmp. - * */ -var gdprConsent = { - hasConsent: function (options, consentCb) { - options = options || { consentTimeout: 1500 }; - var stopTimer; - var processed = false; - var consentAnswer = false; - if (typeof (window.__cmp) !== 'undefined') { - stopTimer = setTimeout(function () { - consentAnswer = false; - processed = true; - consentCb(consentAnswer); - }, options.consentTimeout); - - window.__cmp('ping', null, function(pingAnswer) { - if (pingAnswer.gdprAppliesGlobally) { - window.__cmp('getVendorConsents', [DT_VENDOR_ID], function (result) { - if (processed) { return; } // timeout before cmp answer, cancel - clearTimeout(stopTimer); - var myconsent = result.vendorConsents[DT_VENDOR_ID]; - consentCb(myconsent); - }); - } else { - if (processed) { return; } // timeout before cmp answer, cancel - clearTimeout(stopTimer); - consentAnswer = true; - consentCb(consentAnswer); - } - }); - } else { - // __cmp library is not preset. - // ignore this check and rely on id system GDPR consent management - consentAnswer = true; - consentCb(consentAnswer); - } - } -} - -/** - * Encapsulation of needed info for the callback return. - * - * @param {any} opts - */ -var ResultWrapper = function (opts) { - var me = this; - this.idObj = null; - - var idSystemFn = null; - - /** - * Callback method that is passed back to the userId module. - * - * @param {function} callback - */ - this.userIdCallback = function (callback) { - idSystemFn = callback; - if (me.idObj == null) { - me.idObj = _savedId; - } - - if (me.idObj != null && isFunc(callback)) { - callback(wrapIdResult()); - } - } - - /** - * Return a wrapped result formatted for userId system - */ - function wrapIdResult() { - if (me.idObj == null) { - me.idObj = _savedId; - } - - if (me.idObj == null) { - return null; - } - - var cp = me.configParams; - var exp = (cp && cp.storage && cp.storage.expires) || 60; - - var rslt = { - data: null, - expires: exp - }; - if (me.idObj && me.idObj.success && me.idObj.identity) { - rslt.data = me.idObj.identity; - } else { - rslt.err = 'Failure getting id'; - } - - return rslt; - } - - this.retries = 0; - this.retryId = 0; - - this.executeIdRequest = function (configParams) { - // eslint-disable-next-line no-undef - DigiTrust.getUser({ member: 'prebid' }, function (idResult) { - me.idObj = idResult; - var cb = function () { - if (isFunc(idSystemFn)) { - idSystemFn(wrapIdResult()); - } - } - - cb(); - if (configParams && configParams.callback && isFunc(configParams.callback)) { - try { - configParams.callback(idResult); - } catch (ex) { - utils.logError('Failure in DigiTrust executeIdRequest', ex); - } - } - }); - } -} - -// An instance of the result wrapper object. -var resultHandler = new ResultWrapper(); - -/* - * Internal implementation to get the Id and trigger callback - */ -function getDigiTrustId(configParams) { - if (resultHandler.configParams == null) { - resultHandler.configParams = configParams; - } - - // First see if we should initialize DigiTrust framework - if (isPresent() && !isInitialized()) { - initializeDigiTrust(configParams); - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - return resultHandler.userIdCallback; - } else if (!isInitialized()) { // Second see if we should build a facade object - if (resultHandler.retries >= MAX_RETRIES) { - initDigitrustFacade(configParams); // initialize a facade object that relies on the AJAX call - resultHandler.executeIdRequest(configParams); - } else { - // use expanding envelope - if (resultHandler.retryId != 0) { - clearTimeout(resultHandler.retryId); - } - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - } - return resultHandler.userIdCallback; - } else { // Third get the ID - resultHandler.executeIdRequest(configParams); - return resultHandler.userIdCallback; - } -} - -function initializeDigiTrust(config) { - utils.logInfo('Digitrust Init'); - var dt = window.DigiTrust; - if (dt && !dt.isClient && config != null) { - dt.initialize(config.init, config.callback); - } else if (dt == null) { - // Assume we are already on a delay and DigiTrust is not on page - initDigitrustFacade(config); - } -} - -var testHook = {}; - -/** - * Exposes the test hook object by attaching to the digitrustIdModule. - * This method is called in the unit tests to surface internals. - */ -export function surfaceTestHook() { - digiTrustIdSubmodule['_testHook'] = testHook; - return testHook; -} - -testHook.initDigitrustFacade = initDigitrustFacade; // expose for unit tests -testHook.gdpr = gdprConsent; - -/** @type {Submodule} */ -export const digiTrustIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: 'digitrust', - /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{pubcid:string}} - */ - decode: function (idData) { - try { - return { 'digitrustid': idData }; - } catch (e) { - utils.logError('DigiTrust ID submodule decode error'); - } - }, - getId: function (configParams) { - return {callback: getDigiTrustId(configParams)}; - }, - _testInit: surfaceTestHook -}; - -// check for fallback init of DigiTrust -function fallbackInit() { - if (resultHandler.retryId == 0 && !isInitialized()) { - // this triggers an init - var conf = { - member: 'fallback', - callback: noop - }; - getDigiTrustId(conf); - } -} - -fallbackTimer = setTimeout(fallbackInit, fallbackTimeout); - -submodule('userId', digiTrustIdSubmodule); diff --git a/modules/digiTrustIdSystem.md b/modules/digiTrustIdSystem.md deleted file mode 100644 index c0b274d3292..00000000000 --- a/modules/digiTrustIdSystem.md +++ /dev/null @@ -1,156 +0,0 @@ -## DigiTrust Universal Id Integration - -Setup ------ -The DigiTrust Id integration for Prebid may be used with or without the full -DigiTrust library. This is an optional module that must be used in conjunction -with the userId module. - -See the [Prebid Integration Guide for DigiTrust](https://github.com/digi-trust/dt-cdn/wiki/Prebid-Integration-for-DigiTrust-Id) -and the [DigiTrust wiki](https://github.com/digi-trust/dt-cdn/wiki) -for further instructions. - - -## Example Prebid Configuration for Digitrust Id -``` - pbjs.que.push(function() { - pbjs.setConfig({ - usersync: { - userIds: [{ - name: "digitrust", - params: { - init: { - member: 'example_member_id', - site: 'example_site_id' - }, - callback: function (digiTrustResult) { - // This callback method is optional and used for error handling - // in many if not most cases. - /* - if (digiTrustResult.success) { - // Success in Digitrust init; - // 'DigiTrust Id (encrypted): ' + digiTrustResult.identity.id; - } - else { - // Digitrust init failed - } - */ - } - }, - storage: { - type: "html5", - name: "pbjsdigitrust", - expires: 60 - } - }] - } - }); - pbjs.addAdUnits(adUnits); - pbjs.requestBids({ - bidsBackHandler: sendAdserverRequest - }); - }); - -``` - - -## Building Prebid with DigiTrust Support -Your Prebid build must include the modules for both **userId** and **digitrustIdLoader**. 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,digitrustIdLoader - -### Step by step Prebid build instructions for DigiTrust - -1. Download the Prebid source from [Prebid Git Repo](https://github.com/prebid/Prebid.js) -2. Set up your environment as outlined in the [Readme File](https://github.com/prebid/Prebid.js/blob/master/README.md#Build) -3. Execute the build command either with all modules or with the `userId` and `digitrustIdLoader` modules. - ``` - $ gulp build --modules=userId,digitrustIdLoader - ``` -4. (Optional) Concatenate the DigiTrust source code to the end of your `prebid.js` file for a single source distribution. -5. Upload the resulting source file to your CDN. - - -## Deploying Prebid with DigiTrust ID support -**Precondition:** You must be a DigiTrust member and have registered through the [DigiTrust Signup Process](http://www.digitru.st/signup/). -Your assigned publisher ID will be required in the configuration settings for all deployment scenarios. - -There are three supported approaches to deploying the Prebid-integrated DigiTrust package: - -* "Bare bones" deployment using only the integrated DigiTrust module code. -* Full DigiTrust with CDN referenced DigiTrust.js library. -* Full DigiTrust packaged with Prebid or site js. - -### Bare Bones Deployment - -This deployment results in the smallest Javascript package and is the simplest deployment. -It is appropriate for testing or deployments where simplicity is key. This approach -utilizes the REST API for ID generation. While there is less Javascript in use, -the user may experience more network requests than the scenarios that include the full -DigiTrust library. - -1. Build your Prebid package as above, skipping step 4. -2. Add the DigiTrust initializer section to your Prebid initialization object as below, - using your Member ID and Site ID. -3. Add a reference to your Prebid package and the initialization code on all pages you wish - to utilize Prebid with integrated DigiTrust ID. - - - - -### Full DigiTrust with CDN referenced DigiTrust library - -Both "Full DigiTrust" deployments will result in a larger initial Javascript payload. -The end user may experience fewer overall network requests as the encrypted and anonymous -DigiTrust ID can often be generated fully in client-side code. Utilizing the CDN reference -to the official DigiTrust distribution insures you will be running the latest version of the library. - -The Full DigiTrust deployment is designed to work with both new DigiTrust with Prebid deployments, and with -Prebid deployments by existing DigiTrust members. This allows you to migrate your code more slowly -without losing DigiTrust support in the process. - -1. Deploy your built copy of `prebid.js` to your CDN. -2. On each page reference both your `prebid.js` and a copy of the **DigiTrust** library. - This may either be a copy downloaded from the [DigiTrust CDN](https://cdn.digitru.st/prod/1/digitrust.min.js) to your CDN, - or directly referenced from the URL https://cdn.digitru.st/prod/1/digitrust.min.js. These may be added to the page in any order. -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - -### Full DigiTrust packaged with Prebid - - -1. Deploy your built copy of `prebid.js` to your CDN. Be sure to perform *Step 4* of the build to concatenate or - integrate the full DigiTrust library code with your Prebid package. -2. On each page reference your `prebid.js` -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - This code may also be appended to your Prebid package or placed in other initialization methods. - - - -## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the DigiTrust ID integration. - -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the DigiTrust module - `"digitrust"` | `"digitrust"` | -| params | Required | Object | Details for DigiTrust initialization. | | -| params.init | Required | Object | Initialization parameters, including the DigiTrust Publisher ID and Site ID. | | -| params.init.member | Required | String | DigiTrust Publisher Id | "A897dTzB" | -| params.init.site | Required | String | DigiTrust Site Id | "MM2123" | -| params.callback | Optional | Function | Callback method to fire after initialization of the DigiTrust framework. The argument indicates failure and success and the identity object upon success. | | -| 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. | `"pbjsdigitrust"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Default is 30 for UnifiedId and 1825 for PubCommonID | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the Unified 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 | `{"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3"}` | - - - -## Further Reading - -+ [DigiTrust Home Page](http://digitru.st) - -+ [DigiTrust integration guide](https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide) - -+ [DigiTrust ID Encryption](https://github.com/digi-trust/dt-cdn/wiki/ID-encryption) - diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 03dca0b607d..a35965c4cb4 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -1,46 +1,71 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'districtmDMX'; const DMXURI = 'https://dmx.districtm.io/b/v1'; +const GVLID = 144; +const VIDEO_MAPPING = { + playback_method: { + 'auto_play_sound_on': 1, + 'auto_play_sound_off': 2, + 'click_to_play': 3, + 'mouse_over': 4, + 'viewport_sound_on': 5, + 'viewport_sound_off': 6 + } +}; export const spec = { code: BIDDER_CODE, - supportedFormat: ['banner'], + gvlid: GVLID, + aliases: ['dmx'], + supportedFormat: [BANNER, VIDEO], + supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid(bid) { - return !!(bid.params.dmxid && bid.params.memberid); + return !!(bid.params.memberid); }, interpretResponse(response, bidRequest) { response = response.body || {}; if (response.seatbid) { if (utils.isArray(response.seatbid)) { - const {seatbid} = response; + const { seatbid } = response; let winners = seatbid.reduce((bid, ads) => { - let ad = ads.bid.reduce(function(oBid, nBid) { + let ad = ads.bid.reduce(function (oBid, nBid) { if (oBid.price < nBid.price) { const bid = matchRequest(nBid.impid, bidRequest); - const {width, height} = defaultSize(bid); + const { width, height } = defaultSize(bid); nBid.cpm = parseFloat(nBid.price).toFixed(2); nBid.bidId = nBid.impid; nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; + nBid.ttl = 300; + nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; + if (nBid.mediaType === 'video') { + nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); + nBid.ttl = 3600; + } if (nBid.dealid) { nBid.dealId = nBid.dealid; } + nBid.uuid = nBid.bidId; nBid.ad = nBid.adm; nBid.netRevenue = true; nBid.creativeId = nBid.crid; nBid.currency = 'USD'; - nBid.ttl = 60; + nBid.meta = nBid.meta || {}; + if (nBid.adomain && nBid.adomain.length > 0) { + nBid.meta.advertiserDomains = nBid.adomain; + } return nBid; } else { oBid.cpm = oBid.price; return oBid; } - }, {price: 0}); + }, { price: 0 }); if (ad.adm) { bid.push(ad) } @@ -77,18 +102,24 @@ export const spec = { let params = config.getConfig('dmx'); dmxRequest.user = params.user || {}; let site = params.site || {}; - dmxRequest.site = {...dmxRequest.site, ...site} + dmxRequest.site = { ...dmxRequest.site, ...site } } catch (e) { } let eids = []; - if (bidRequest && bidRequest.userId) { - bindUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); - bindUserId(eids, utils.deepAccess(bidRequest, `userId.digitrustid.data.id`), 'digitru.st', 1); - bindUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); - bindUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); - bindUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 1); + if (bidRequest[0] && bidRequest[0].userId) { + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.idl_env`), 'liveramp.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.id5id.uid`), 'id5-sync.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.pubcid`), 'pubcid.org', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.tdid`), 'adserver.org', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.criteoId`), 'criteo.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.britepoolid`), 'britepool.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.lipb.lipbid`), 'liveintent.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.intentiqid`), 'intentiq.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.lotamePanoramaId`), 'lotame.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.parrableId`), 'parrable.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.netId`), 'netid.de', 1); dmxRequest.user = dmxRequest.user || {}; dmxRequest.user.ext = dmxRequest.user.ext || {}; dmxRequest.user.ext.eids = eids; @@ -100,9 +131,12 @@ export const spec = { dmxRequest.regs = {}; dmxRequest.regs.ext = {}; dmxRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - dmxRequest.user = {}; - dmxRequest.user.ext = {}; - dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; + + if (bidderRequest.gdprConsent.gdprApplies === true) { + dmxRequest.user = {}; + dmxRequest.user.ext = {}; + dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; + } } dmxRequest.regs = dmxRequest.regs || {}; dmxRequest.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; @@ -116,20 +150,37 @@ export const spec = { dmxRequest.source = {}; dmxRequest.source.ext = {}; dmxRequest.source.ext.schain = schain || {} - } catch (e) {} + } catch (e) { } let tosendtags = bidRequest.map(dmx => { var obj = {}; obj.id = dmx.bidId; - obj.tagid = String(dmx.params.dmxid); + obj.tagid = String(dmx.params.dmxid || dmx.adUnitCode); obj.secure = 1; - obj.banner = { - topframe: 1, - w: cleanSizes(dmx.sizes, 'w'), - h: cleanSizes(dmx.sizes, 'h'), - format: cleanSizes(dmx.sizes).map(s => { - return {w: s[0], h: s[1]}; - }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') - }; + obj.bidfloor = getFloor(dmx); + if (dmx.mediaTypes && dmx.mediaTypes.video) { + obj.video = { + topframe: 1, + skip: dmx.mediaTypes.video.skip || 0, + linearity: dmx.mediaTypes.video.linearity || 1, + minduration: dmx.mediaTypes.video.minduration || 5, + maxduration: dmx.mediaTypes.video.maxduration || 60, + playbackmethod: dmx.mediaTypes.video.playbackmethod || [2], + api: getApi(dmx.mediaTypes.video), + mimes: dmx.mediaTypes.video.mimes || ['video/mp4'], + protocols: getProtocols(dmx.mediaTypes.video), + h: dmx.mediaTypes.video.playerSize[0][1], + w: dmx.mediaTypes.video.playerSize[0][0] + }; + } else { + obj.banner = { + topframe: 1, + w: cleanSizes(dmx.sizes, 'w'), + h: cleanSizes(dmx.sizes, 'h'), + format: cleanSizes(dmx.sizes).map(s => { + return { w: s[0], h: s[1] }; + }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') + }; + } return obj; }); @@ -169,6 +220,27 @@ export const spec = { } } +export function getFloor(bid) { + let floor = null; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: bid.mediaTypes.video ? 'video' : 'banner', + size: bid.sizes.map(size => { + return { + 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; +} + export function cleanSizes(sizes, value) { const supportedSize = [ { @@ -228,7 +300,7 @@ export function shuffle(sizes, list) { } results.push(current); results = list.filter(l => results.map(r => `${r[0]}x${r[1]}`).indexOf(`${l.size[0]}x${l.size[1]}`) !== -1); - results = results.sort(function(a, b) { + results = results.sort(function (a, b) { return b.s - a.s; }) return results.map(r => r.size); @@ -274,7 +346,7 @@ export function upto5(allimps, dmxRequest, bidderRequest, DMXURI) { * */ export function matchRequest(id, bidRequest) { - const {bids} = bidRequest.bidderRequest; + const { bids } = bidRequest.bidderRequest; const [returnValue] = bids.filter(bid => bid.bidId === id); return returnValue; } @@ -290,7 +362,7 @@ export function checkDeepArray(Arr) { } } export function defaultSize(thebidObj) { - const {sizes} = thebidObj; + const { sizes } = thebidObj; const returnObject = {}; returnObject.width = checkDeepArray(sizes)[0]; returnObject.height = checkDeepArray(sizes)[1]; @@ -310,4 +382,52 @@ export function bindUserId(eids, value, source, atype) { }) } } + +export function getApi({ api }) { + let defaultValue = [2]; + if (api && Array.isArray(api) && api.length > 0) { + return api + } else { + return defaultValue; + } +} +export function getPlaybackmethod(playback) { + if (Array.isArray(playback) && playback.length > 0) { + return playback.map(label => { + return VIDEO_MAPPING.playback_method[label] + }) + } + return [2] +} + +export function getProtocols({ protocols }) { + let defaultValue = [2, 3, 5, 6, 7, 8]; + if (protocols && Array.isArray(protocols) && protocols.length > 0) { + return protocols; + } else { + return defaultValue; + } +} + +export function cleanVast(str, nurl) { + try { + const toberemove = /]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/ + const [img, url] = str.match(toberemove) + str = str.replace(toberemove, '') + if (img) { + if (url) { + const insrt = `` + str = str.replace('', `${insrt}`) + } + } + return str; + } catch (e) { + if (!nurl) { + return str + } + const insrt = `` + str = str.replace('', `${insrt}`) + return str + } +} registerBidder(spec); diff --git a/modules/districtmDmxBidAdapter.md b/modules/districtmDmxBidAdapter.md index 792cf2e7305..5d5dd2affe6 100644 --- a/modules/districtmDmxBidAdapter.md +++ b/modules/districtmDmxBidAdapter.md @@ -20,13 +20,14 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman ## Media Types * Banner - +* Video ## Bidder Parameters | Key | Scope | Type | Description | --- | --- | --- | --- | `dmxid` | Mandatory | Integer | Unique identifier of the placement, dmxid can be obtained in the district m Boost platform. | `memberid` | Mandatory | Integer | Unique identifier for your account, memberid can be obtained in the district m Boost platform. +| `floor` | Optional | float | Most placement can have floor set in our platform, but this can now be set on the request too. # Ad Unit Configuration Example @@ -48,6 +49,35 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman }]; ``` +# Ad Unit Configuration Example for video request + +```javascript + var videoAdUnit = { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: {context: 'instream', //or 'outstream' + playerSize: [[640, 480]], + skipppable: true, + minduration: 5, + maxduration: 45, + playback_method: ['auto_play_sound_off', 'viewport_sound_off'], + mimes: ["application/javascript", + "video/mp4"], + + } }, + bids: [ + { + bidder: 'districtmDMX', + params: { + dmxid: '100001', + memberid: '100003', + } + } + + ] + }; +``` + # Ad Unit Configuration when COPPA is needed @@ -117,6 +147,35 @@ Our demand and adapter supports multiple sizes per placement, as such a single d }]; ``` +Our bidder only supports instream context at the moment and we strongly like to put the media types and setting in the ad unit settings. +If no value is set the default value will be applied. + +```javascript + var videoAdUnit = { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: {context: 'instream', //or 'outstream' + playerSize: [[640, 480]], + skipppable: true, + minduration: 5, + maxduration: 45, + playback_method: ['auto_play_sound_off', 'viewport_sound_off'], + mimes: ["application/javascript", + "video/mp4"], + + } }, + bids: [ + { + bidder: 'districtmDMX', + params: { + dmxid: '250258', + memberid: '100600', + } + } + ] + }; +``` + ###### 4. Implementation Checking Once the bidder is live in your Prebid configuration you may confirm it is making requests to our end point by looking for requests to `https://dmx.districtm.io/b/v1`. diff --git a/modules/djaxBidAdapter.js b/modules/djaxBidAdapter.js deleted file mode 100644 index ffaf61a3f15..00000000000 --- a/modules/djaxBidAdapter.js +++ /dev/null @@ -1,129 +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 = 'djax'; -const DOMAIN = 'https://demo.reviveadservermod.com/headerbidding_adminshare/'; -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) { - bidAd.adResponse = { - content: bidAd.vastXml, - height: bidAd.height, - width: bidAd.width - }; - bidAd.ttl = config.getConfig('_bidderTimeout') - bidAd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, { - id: bidAd.adUnitCode, - url: RENDERER_URL - }, bidAd.adUnitCode) : undefined; - bidResponses.push(bidAd); - }); - - 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, rendererParams, adUnitCode) { - const renderer = Renderer.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false, - config: {'player_height': bidAd.height, 'player_width': bidAd.width}, - 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/dmdIdSystem.js b/modules/dmdIdSystem.js new file mode 100644 index 00000000000..72d3518c20f --- /dev/null +++ b/modules/dmdIdSystem.js @@ -0,0 +1,91 @@ +/** + * This module adds dmdId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/dmdIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; + +const MODULE_NAME = 'dmdId'; + +/** @type {Submodule} */ +export const dmdIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(value) { + return value && typeof value === 'string' + ? { 'dmdId': value } + : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleConfig} [config] + * @param {ConsentData} + * @param {Object} cacheIdObj - existing id, if any consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData, cacheIdObj) { + const configParams = (config && config.params) || {}; + if ( + !configParams || + !configParams.api_key || + typeof configParams.api_key !== 'string' + ) { + utils.logError('dmd submodule requires an api_key.'); + return; + } + // If cahceIdObj is null or undefined - calling AIX-API + if (cacheIdObj) { + return cacheIdObj; + } else { + const url = configParams && configParams.api_url + ? configParams.api_url + : `https://aix.hcn.health/api/v1/auths`; + // Setting headers + const headers = {}; + headers['x-api-key'] = configParams.api_key; + headers['x-domain'] = utils.getWindowLocation(); + // Response callbacks + const resp = function (callback) { + const callbacks = { + success: response => { + let responseObj; + let responseId; + try { + responseObj = JSON.parse(response); + if (responseObj && responseObj.dgid) { + responseId = responseObj.dgid; + } + } catch (error) { + utils.logError(error); + } + callback(responseId); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true, customHeaders: headers }); + }; + return { callback: resp }; + } + } +}; + +submodule('userId', dmdIdSubmodule); diff --git a/modules/dmdIdSystem.md b/modules/dmdIdSystem.md new file mode 100644 index 00000000000..f2a5b76ade7 --- /dev/null +++ b/modules/dmdIdSystem.md @@ -0,0 +1,26 @@ +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'dmdId', + storage: { + name: 'dmd-dgid', + type: 'cookie', + expires: 30 + }, + params: { + api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by DMD + } + }] + } +}); + +#### DMD ID Configuration + +{: .table .table-bordered .table-striped } +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of Module | `"dmdId"` | +| storage | Required | Object | | +| storage.name | Required | String | `dmd-dgid` | +| params | Required | Object | Container of all module params. | | +| params.api_key | Required | String | This is your `api_key` as provided by DMD Marketing Corp. | `3fdbe297-3690-4f5c-9e11-ee9186a6d77c` | \ No newline at end of file diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js new file mode 100644 index 00000000000..f9f3e1bcc70 --- /dev/null +++ b/modules/docereeBidAdapter.js @@ -0,0 +1,65 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'doceree'; +const END_POINT = 'https://bidder.doceree.com' + +export const spec = { + code: BIDDER_CODE, + url: '', + supportedMediaTypes: [ BANNER ], + + isBidRequestValid: (bid) => { + const { placementId } = bid.params; + return !!placementId + }, + buildRequests: (validBidRequests) => { + const serverRequests = []; + const { data } = config.getConfig('doceree.user') + const { page, domain, token } = config.getConfig('doceree.context') + const encodedUserInfo = window.btoa(encodeURIComponent(JSON.stringify(data))) + + validBidRequests.forEach(function(validBidRequest) { + const { publisherUrl, placementId } = validBidRequest.params; + const url = publisherUrl || page + let queryString = ''; + queryString = utils.tryAppendQueryString(queryString, 'id', placementId); + queryString = utils.tryAppendQueryString(queryString, 'publisherDomain', domain); + queryString = utils.tryAppendQueryString(queryString, 'pubRequestedURL', encodeURIComponent(url)); + queryString = utils.tryAppendQueryString(queryString, 'loggedInUser', encodedUserInfo); + queryString = utils.tryAppendQueryString(queryString, 'currentUrl', url); + queryString = utils.tryAppendQueryString(queryString, 'prebidjs', true); + queryString = utils.tryAppendQueryString(queryString, 'token', token); + queryString = utils.tryAppendQueryString(queryString, 'requestId', validBidRequest.bidId); + + serverRequests.push({ + method: 'GET', + url: END_POINT + '/v1/adrequest?' + queryString + }) + }) + return serverRequests; + }, + interpretResponse: (serverResponse, request) => { + const responseJson = serverResponse ? serverResponse.body : {}; + const placementId = responseJson.DIVID; + const bidResponse = { + ad: responseJson.sourceHTML, + width: Number(responseJson.width), + height: Number(responseJson.height), + requestId: responseJson.guid, + netRevenue: true, + ttl: 30, + cpm: responseJson.cpmBid, + currency: responseJson.currency, + mediaType: 'banner', + creativeId: placementId, + meta: { + advertiserDomains: [responseJson.advertiserDomain] + } + }; + return [bidResponse]; + } +}; + +registerBidder(spec); diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md new file mode 100644 index 00000000000..d977e11f40a --- /dev/null +++ b/modules/docereeBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Doceree Bidder Adapter +Module Type: Bidder Adapter +Maintainer: sourbh.gupta@doceree.com +``` + + +Connects to Doceree demand source to fetch bids. +Please use ```doceree``` as the bidder code. + + +# Test Parameters +``` +var adUnits = [ + { + code: 'doceree', + sizes: [ + [300, 250] + ], + bids: [ + { + bidder: "doceree", + params: { + placementId: 'DOC_7jm9j5eqkl0xvc5w', //required + publisherUrl: document.URL || window.location.href, //optional + } + } + ] + } +]; +``` diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index d05549601e1..df0f6f3b8ea 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -7,9 +7,11 @@ const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const DEFAULT_VAST_FORMAT = 'vast2'; +const GVLID = 602; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['dspx'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { @@ -19,10 +21,6 @@ export const spec = { 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); const referrer = bidderRequest.refererInfo.referer; @@ -32,26 +30,28 @@ export const spec = { let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; let payload = {}; - if (isVideoRequest(bidRequest)) { - let vastFormat = params.vastFormat || DEFAULT_VAST_FORMAT; + if (isBannerRequest(bidRequest)) { + let size = getBannerSizes(bidRequest)[0]; payload = { - _f: vastFormat, + _f: 'html', alternative: 'prebid_js', inventory_item_id: placementId, - srw: width, - srh: height, + srw: size.width, + srh: size.height, idt: 100, rnd: rnd, ref: referrer, bid_id: bidId, }; } else { + let size = getVideoSizes(bidRequest)[0]; + let vastFormat = params.vastFormat || DEFAULT_VAST_FORMAT; payload = { - _f: 'html', + _f: vastFormat, alternative: 'prebid_js', inventory_item_id: placementId, - srw: width, - srh: height, + srw: size.width, + srh: size.height, idt: 100, rnd: rnd, ref: referrer, @@ -86,6 +86,14 @@ export const spec = { if (isDev) { payload.prebidDevMode = 1; } + + if (bidRequest.userId && bidRequest.userId.netId) { + payload.did_netid = bidRequest.userId.netId; + } + if (bidRequest.userId && bidRequest.userId.uid2) { + payload.did_uid2 = bidRequest.userId.uid2; + } + return { method: 'GET', url: endpoint, @@ -112,7 +120,10 @@ export const spec = { currency: currency, netRevenue: netRevenue, type: response.type, - ttl: config.getConfig('_bidderTimeout') + ttl: config.getConfig('_bidderTimeout'), + meta: { + advertiserDomains: response.adomain || [] + } }; if (response.vastXml) { bidResponse.vastXml = response.vastXml; @@ -120,10 +131,50 @@ export const spec = { } else { bidResponse.ad = response.adTag; } + bidResponses.push(bidResponse); } return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = [] + + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + 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; + } +} + +function appendToUrl(url, what) { + if (!what) { + return url; } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; } function objectToQueryString(obj, prefix) { @@ -141,6 +192,16 @@ function objectToQueryString(obj, prefix) { return str.join('&'); } +/** + * 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' || !!utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); +} + /** * Check if it's a video bid request * @@ -151,4 +212,48 @@ function isVideoRequest(bid) { return bid.mediaType === 'video' || !!utils.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(utils.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(utils.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/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index c30c10d8a90..cfbbbee61cb 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -98,7 +98,11 @@ export const spec = { height: ebdrBid.h, currency: 'USD', netRevenue: true, - ttl: 3600 } + ttl: 3600, + meta: { + advertiserDomains: ebdrBid.adomain || [] + } + }; if (vastURL) { response.vastUrl = vastURL; } diff --git a/modules/edgequeryxBidAdapter.js b/modules/edgequeryxBidAdapter.js deleted file mode 100644 index ee50946ee18..00000000000 --- a/modules/edgequeryxBidAdapter.js +++ /dev/null @@ -1,98 +0,0 @@ -import * as utils from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'edgequeryx'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['eqx'], // short 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 && bid.params.accountId && bid.params.widgetId); - }, - - /** - * 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) { - // 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 - - // pull requested transaction ID from bidderRequest.bids[].transactionId - return validBidRequests.map(bid => { - // Common bid request attributes for banner, outstream and instream. - let payload = { - accountId: bid.params.accountId, - widgetId: bid.params.widgetId, - currencyCode: 'EUR', - tagId: bid.adUnitCode, - transactionId: bid.transactionId, - timeout: config.getConfig('bidderTimeout'), - bidId: bid.bidId, - prebidVersion: '$prebid.version$' - }; - - const bannerMediaType = utils.deepAccess(bid, 'mediaTypes.banner'); - payload.sizes = bannerMediaType.sizes.map(size => ({ - w: size[0], - h: size[1] - })); - - var payloadString = JSON.stringify(payload); - - return { - method: 'POST', - url: (bid.params.domain !== undefined ? bid.params.domain : 'https://deep.edgequery.io') + '/prebid/x', - data: payloadString, - }; - }); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequestString - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequestString) { - const bidResponses = []; - let response = serverResponse.body; - try { - if (response) { - let bidResponse = { - requestId: response.requestId, - cpm: response.cpm, - currency: response.currency, - width: response.width, - height: response.height, - ad: response.ad, - ttl: response.ttl, - creativeId: response.creativeId, - netRevenue: response.netRevenue - }; - - bidResponses.push(bidResponse); - } - } catch (error) { - utils.logError('Error while parsing Edge Query X response', error); - } - return bidResponses; - } - -}; - -registerBidder(spec); diff --git a/modules/emoteevBidAdapter.js b/modules/emoteevBidAdapter.js deleted file mode 100644 index e0f88725d8a..00000000000 --- a/modules/emoteevBidAdapter.js +++ /dev/null @@ -1,525 +0,0 @@ -/** - * This file contains Emoteev bid adpater. - * - * It is organised as follows: - * - Constants values; - * - Spec API functions, which should be pristine pure; - * - Ancillary functions, which should be as pure as possible; - * - Adapter API, where unpure side-effects happen. - * - * The code style is « functional core, imperative shell ». - * - * @link https://www.emoteev.io - * @file This files defines the spec of EmoteevBidAdapter. - * @author Emoteev Engineering . - */ - -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import { - triggerPixel, - getUniqueIdentifierStr, - contains, - deepAccess, - isArray, - isInteger, - getParameterByName, - buildUrl -} from '../src/utils.js'; -import {config} from '../src/config.js'; -import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); - -export const BIDDER_CODE = 'emoteev'; - -/** - * Version number of the adapter API. - */ -export const ADAPTER_VERSION = '1.35.0'; - -export const DOMAIN = 'prebid.emoteev.xyz'; -export const DOMAIN_STAGING = 'prebid-staging.emoteev.xyz'; -export const DOMAIN_DEVELOPMENT = 'localhost:3000'; - -/** - * Path of Emoteev endpoint for events. - */ -export const EVENTS_PATH = '/api/ad_event.json'; - -/** - * Path of Emoteev bidder. - */ -export const BIDDER_PATH = '/api/prebid/bid'; -export const USER_SYNC_IFRAME_PATH = '/api/prebid/sync-iframe'; -export const USER_SYNC_IMAGE_PATH = '/api/prebid/sync-image'; - -export const PRODUCTION = 'production'; -export const STAGING = 'staging'; -export const DEVELOPMENT = 'development'; -export const DEFAULT_ENV = PRODUCTION; - -export const ON_ADAPTER_CALLED = 'on_adapter_called'; -export const ON_BID_WON = 'on_bid_won'; -export const ON_BIDDER_TIMEOUT = 'on_bidder_timeout'; - -export const IN_CONTENT = 'content'; -export const FOOTER = 'footer'; -export const OVERLAY = 'overlay'; -export const WALLPAPER = 'wallpaper'; - -/** - * Vendor ID assigned to Emoteev from the Global Vendor & CMP List. - * - * See https://vendorlist.consensu.org/vendorinfo.json for more information. - * @type {number} - */ -export const VENDOR_ID = 15; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#valid-build-requests-array for detailed semantic. - * - * @param {AdUnit.bidRequest} bidRequest - * @returns {boolean} Is this bidRequest valid? - */ -export const isBidRequestValid = (bidRequest) => { - return !!( - bidRequest && - bidRequest.params && - deepAccess(bidRequest, 'params.adSpaceId') && - validateContext(deepAccess(bidRequest, 'params.context')) && - validateExternalId(deepAccess(bidRequest, 'params.externalId')) && - bidRequest.bidder === BIDDER_CODE && - validateSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'))); -}; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#serverrequest-objects for detailed semantic. - * - * @param {string} env Emoteev environment parameter - * @param {boolean} debug Pbjs debug parameter. - * @param {string} currency See http://prebid.org/dev-docs/modules/currency.html for detailed semantic. - * @param {Array} validBidRequests Takes an array of bid requests, which are guaranteed to have passed the isBidRequestValid() test. - * @param bidderRequest General context for a bidder request being constructed - * @returns {ServerRequest} - */ -export const buildRequests = (env, debug, currency, validBidRequests, bidderRequest) => { - return { - method: 'POST', - url: bidderUrl(env), - data: JSON.stringify(requestsPayload(debug, currency, validBidRequests, bidderRequest)) // Keys with undefined values will be filtered out. - }; -}; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response for detailed semantic. - * - * @param {Array} serverResponse.body The body of the server response is an array of bid objects. - * @returns {Array} - */ -export const interpretResponse = (serverResponse) => serverResponse.body; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-set-targeting for detailed semantic. - * - * @param {string} env Emoteev environment parameter. - * @param {BidRequest} bidRequest - * @returns {UrlObject} - */ -export function onAdapterCalled(env, bidRequest) { - return { - protocol: 'https', - hostname: domain(env), - pathname: EVENTS_PATH, - search: { - eventName: ON_ADAPTER_CALLED, - pubcId: deepAccess(bidRequest, 'crumbs.pubcid'), - bidId: bidRequest.bidId, - adSpaceId: deepAccess(bidRequest, 'params.adSpaceId'), - cache_buster: getUniqueIdentifierStr() - } - }; -} - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-bid-won for detailed semantic. - * - * @param {string} env Emoteev environment parameter. - * @param {string} pubcId Publisher common id. See http://prebid.org/dev-docs/modules/pubCommonId.html for detailed semantic. - * @param bidObject - * @returns {UrlObject} - */ -export const onBidWon = (env, pubcId, bidObject) => { - const bidId = bidObject.requestId; - return { - protocol: 'https', - hostname: domain(env), - pathname: EVENTS_PATH, - search: { - eventName: ON_BID_WON, - pubcId, - bidId, - cache_buster: getUniqueIdentifierStr() - } - }; -}; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-timeout for detailed semantic. - * - * @param {string} env Emoteev environment parameter. - * @param {BidRequest} bidRequest - * @returns {UrlObject} - */ -export const onTimeout = (env, bidRequest) => { - return { - protocol: 'https', - hostname: domain(env), - pathname: EVENTS_PATH, - search: { - eventName: ON_BIDDER_TIMEOUT, - pubcId: deepAccess(bidRequest, 'crumbs.pubcid'), - bidId: bidRequest.bidId, - adSpaceId: deepAccess(bidRequest, 'params.adSpaceId'), - timeout: bidRequest.timeout, - cache_buster: getUniqueIdentifierStr() - } - } -}; - -/** - * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs for detailed semantic. - * - * @param {string} env Emoteev environment parameter - * @param {SyncOptions} syncOptions - * @returns userSyncs - */ -export const getUserSyncs = (env, syncOptions) => { - let syncs = []; - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: userSyncImageUrl(env), - }); - } - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: userSyncIframeUrl(env), - }); - } - return syncs; -}; - -/** - * Pure function. - * - * @param {string} env Emoteev environment parameter - * @returns {string} The domain for network calls to Emoteev. - */ -export const domain = (env) => { - switch (env) { - case DEVELOPMENT: - return DOMAIN_DEVELOPMENT; - case STAGING: - return DOMAIN_STAGING; - default: - return DOMAIN; - } -}; - -/** - * Pure function. - * - * @param {string} env Emoteev environment parameter - * @returns {string} The full URL which events is sent to. - */ -export const eventsUrl = env => buildUrl({ - protocol: (env === DEVELOPMENT) ? 'http' : 'https', - hostname: domain(env), - pathname: EVENTS_PATH -}); - -/** - * Pure function. - * - * @param {string} env Emoteev environment parameter - * @returns {string} The full URL which bidderRequest is sent to. - */ -export const bidderUrl = env => buildUrl({ - protocol: (env === DEVELOPMENT) ? 'http' : 'https', - hostname: domain(env), - pathname: BIDDER_PATH -}); - -/** - * Pure function. - * - * @param {string} env Emoteev environment parameter - * @returns {string} The full URL called for iframe-based user sync - */ -export const userSyncIframeUrl = env => buildUrl({ - protocol: (env === DEVELOPMENT) ? 'http' : 'https', - hostname: domain(env), - pathname: USER_SYNC_IFRAME_PATH -}); - -/** - * Pure function. - * - * @param {string} env Emoteev environment parameter - * @returns {string} The full URL called for image-based user sync - */ -export const userSyncImageUrl = env => buildUrl({ - protocol: (env === DEVELOPMENT) ? 'http' : 'https', - hostname: domain(env), - pathname: USER_SYNC_IMAGE_PATH -}); - -/** - * Pure function. - * - * @param {Array>} sizes - * @returns {boolean} are sizes valid? - */ -export const validateSizes = sizes => isArray(sizes) && sizes.length > 0 && sizes.every(size => isArray(size) && size.length === 2); - -/** - * Pure function. - * - * @param {string} context - * @returns {boolean} is param `context` valid? - */ -export const validateContext = context => contains([IN_CONTENT, FOOTER, OVERLAY, WALLPAPER], context); - -/** - * Pure function. - * - * @param {(number|null|undefined)} externalId - * @returns {boolean} is param `externalId` valid? - */ -export const validateExternalId = externalId => externalId === undefined || externalId === null || (isInteger(externalId) && externalId > 0); - -/** - * Pure function. - * - * @param {BidRequest} bidRequest - * @returns {object} An object which represents a BidRequest for Emoteev server side. - */ -export const conformBidRequest = bidRequest => { - return { - params: bidRequest.params, - crumbs: bidRequest.crumbs, - sizes: bidRequest.sizes, - bidId: bidRequest.bidId, - bidderRequestId: bidRequest.bidderRequestId, - }; -}; - -/** - * Pure function. - * - * @param {object} bidderRequest - * @returns {(boolean|undefined)} raw consent data. - */ -export const gdprConsent = (bidderRequest) => (deepAccess(bidderRequest, 'gdprConsent.vendorData.vendorConsents') || {})[VENDOR_ID]; - -/** - * Pure function. - * - * @param {boolean} debug Pbjs debug parameter - * @param {string} currency See http://prebid.org/dev-docs/modules/currency.html for detailed information - * @param {BidRequest} validBidRequests - * @param {object} bidderRequest - * @returns - */ -export const requestsPayload = (debug, currency, validBidRequests, bidderRequest) => { - return { - akPbjsVersion: ADAPTER_VERSION, - bidRequests: validBidRequests.map(conformBidRequest), - currency: currency, - debug: debug, - language: navigator.language, - refererInfo: bidderRequest.refererInfo, - deviceInfo: getDeviceInfo( - getDeviceDimensions(window), - getViewDimensions(window, document), - getDocumentDimensions(document), - isWebGLEnabled(document)), - userAgent: navigator.userAgent, - gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), - gdprConsent: gdprConsent(bidderRequest), - }; -}; - -/** - * Pure function - * @param {Window} window - * @param {Document} document - * @returns {{width: number, height: number}} View dimensions - */ -export const getViewDimensions = (window, document) => { - let w = window; - let prefix = 'inner'; - - if (window.innerWidth === undefined || window.innerWidth === null) { - w = document.documentElement || document.body; - prefix = 'client'; - } - - return { - width: w[`${prefix}Width`], - height: w[`${prefix}Height`], - }; -}; - -/** - * Pure function - * @param {Window} window - * @returns {{width: number, height: number}} Device dimensions - */ -export const getDeviceDimensions = (window) => { - return { - width: window.screen ? window.screen.width : '', - height: window.screen ? window.screen.height : '', - }; -}; - -/** - * Pure function - * @param {Document} document - * @returns {{width: number, height: number}} Document dimensions - */ -export const getDocumentDimensions = (document) => { - const de = document.documentElement; - const be = document.body; - - const bodyHeight = be ? Math.max(be.offsetHeight, be.scrollHeight) : 0; - - const w = Math.max(de.clientWidth, de.offsetWidth, de.scrollWidth); - const h = Math.max( - de.clientHeight, - de.offsetHeight, - de.scrollHeight, - bodyHeight - ); - - return { - width: isNaN(w) ? '' : w, - height: isNaN(h) ? '' : h, - }; -}; - -/** - * Unpure function - * @param {Document} document - * @returns {boolean} Is WebGL enabled? - */ -export const isWebGLEnabled = (document) => { - // Create test canvas - let canvas = document.createElement('canvas'); - - // The gl context - let gl = null; - - // Try to get the regular WebGL - try { - gl = canvas.getContext('webgl'); - } catch (ex) { - canvas = undefined; - return false; - } - - // No regular WebGL found - if (!gl) { - // Try experimental WebGL - try { - gl = canvas.getContext('experimental-webgl'); - } catch (ex) { - canvas = undefined; - return false; - } - } - - return !!gl; -}; - -/** - * Pure function - * @param {{width: number, height: number}} deviceDimensions - * @param {{width: number, height: number}} viewDimensions - * @param {{width: number, height: number}} documentDimensions - * @param {boolean} webGL - * @returns {object} Device information - */ -export const getDeviceInfo = (deviceDimensions, viewDimensions, documentDimensions, webGL) => { - return { - browserWidth: viewDimensions.width, - browserHeight: viewDimensions.height, - deviceWidth: deviceDimensions.width, - deviceHeight: deviceDimensions.height, - documentWidth: documentDimensions.width, - documentHeight: documentDimensions.height, - webGL: webGL, - }; -}; - -/** - * Pure function - * @param {object} config pbjs config value - * @param {string} parameter Environment override from URL query param. - * @returns {string} One of [PRODUCTION, STAGING, DEVELOPMENT]. - */ -export const resolveEnv = (config, parameter) => { - const configEnv = deepAccess(config, 'emoteev.env'); - - if (contains([PRODUCTION, STAGING, DEVELOPMENT], parameter)) return parameter; - else if (contains([PRODUCTION, STAGING, DEVELOPMENT], configEnv)) return configEnv; - else return DEFAULT_ENV; -}; - -/** - * Pure function - * @param {object} config pbjs config value - * @param {string} parameter Debug override from URL query param. - * @returns {boolean} - */ -export const resolveDebug = (config, parameter) => { - if (parameter && parameter.length && parameter.length > 0) return JSON.parse(parameter); - else if (config.debug) return config.debug; - else return false; -}; - -/** - * EmoteevBidAdapter spec - * @access public - * @type {BidderSpec} - */ -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: isBidRequestValid, - buildRequests: (validBidRequests, bidderRequest) => - buildRequests( - resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), - resolveDebug(config.getConfig(), getParameterByName('debug')), - config.getConfig('currency'), - validBidRequests, - bidderRequest), - interpretResponse: interpretResponse, - onBidWon: (bidObject) => - triggerPixel(buildUrl(onBidWon( - resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), - storage.getCookie('_pubcid'), - bidObject))), - onTimeout: (bidRequest) => - triggerPixel(buildUrl(onTimeout( - resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), - bidRequest))), - getUserSyncs: (syncOptions) => - getUserSyncs( - resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), - syncOptions), -}; - -registerBidder(spec); diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 6688d15d8e9..260ffe105f4 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -11,6 +11,11 @@ const RENDERER_URL = 'https://js.brealtime.com/outstream/1.30.0/bundle.js'; const ADAPTER_VERSION = '1.5.1'; const DEFAULT_CUR = 'USD'; +const EIDS_SUPPORTED = [ + { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' }, + { key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' } +]; + export const emxAdapter = { validateSizes: (sizes) => { if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { @@ -157,11 +162,44 @@ export const emxAdapter = { } return emxData; + }, + getSupplyChain: (bidderRequest, emxData) => { + if (bidderRequest.bids[0] && bidderRequest.bids[0].schain) { + emxData.source = { + ext: { + schain: bidderRequest.bids[0].schain + } + }; + } + + return emxData; + }, + // supporting eids + getEids(bidRequests) { + return EIDS_SUPPORTED + .map(emxAdapter.getUserId(bidRequests)) + .filter(x => x); + }, + getUserId(bidRequests) { + return ({ key, source, rtiPartner }) => { + let id = utils.deepAccess(bidRequests, `userId.${key}`); + return id ? emxAdapter.formatEid(id, source, rtiPartner) : null; + }; + }, + formatEid(id, source, rtiPartner) { + return { + source, + uids: [{ + id, + ext: { rtiPartner } + }] + }; } }; export const spec = { code: BIDDER_CODE, + gvlid: 183, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { if (!bid || !bid.params) { @@ -211,7 +249,7 @@ export const spec = { utils._each(validBidRequests, function (bid) { let tagid = utils.getBidIdParameter('tagid', bid.params); - let bidfloor = parseFloat(utils.getBidIdParameter('bidfloor', bid.params)) || 0; + let bidfloor = parseFloat(getBidFloor(bid)) || 0; let isVideo = !!bid.mediaTypes.video; let data = { id: bid.bidId, @@ -236,9 +274,25 @@ export const spec = { }; emxData = emxAdapter.getGdpr(bidderRequest, Object.assign({}, emxData)); + emxData = emxAdapter.getSupplyChain(bidderRequest, Object.assign({}, emxData)); if (bidderRequest && bidderRequest.uspConsent) { emxData.us_privacy = bidderRequest.uspConsent } + + // adding eid support + if (bidderRequest.userId) { + let eids = emxAdapter.getEids(bidderRequest); + if (eids.length > 0) { + if (emxData.user && emxData.user.ext) { + emxData.user.ext.eids = eids; + } else { + emxData.user = { + ext: {eids} + }; + } + } + } + return { method: 'POST', url, @@ -274,20 +328,55 @@ export const spec = { bidResponse = emxAdapter.formatVideoResponse(bidResponse, Object.assign({}, emxBid), bidRequest); } bidResponse.mediaType = (isVideo ? VIDEO : BANNER); + + // support for adomain in prebid 5.0 + if (emxBid.adomain && emxBid.adomain.length) { + bidResponse.meta = { + advertiserDomains: emxBid.adomain + }; + } + emxBidResponses.push(bidResponse); }); } return emxBidResponses; }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { + let url = 'https://biddr.brealtime.com/check.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}`; + } + } syncs.push({ type: 'iframe', - url: 'https://biddr.brealtime.com/check.html' + url: url }); } return syncs; } }; + +// support floors module in prebid 5.0 +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return parseFloat(utils.getBidIdParameter('bidfloor', bid.params)); + } + + let floor = bid.getFloor({ + currency: DEFAULT_CUR, + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/engageyaBidAdapter.md b/modules/engageyaBidAdapter.md new file mode 100644 index 00000000000..541ba548eeb --- /dev/null +++ b/modules/engageyaBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Engageya's Bidder Adapter +Module Type: Bidder Adapter +Maintainer: reem@engageya.com +``` + +# Description + +Module that connects to Engageya's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [236, 202] + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js new file mode 100644 index 00000000000..e1942fbadc3 --- /dev/null +++ b/modules/enrichmentFpdModule.js @@ -0,0 +1,166 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import * as utils from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getCoreStorageManager } from '../src/storageManager.js'; + +let ortb2 = {}; +let win = (window === window.top) ? window : window.top; +export const coreStorage = getCoreStorageManager('enrichmentFpd'); + +/** + * Find the root domain + * @param {string|undefined} fullDomain + * @return {string} +*/ +export function findRootDomain(fullDomain = window.location.hostname) { + if (!coreStorage.cookiesAreEnabled()) { + return fullDomain; + } + + const domainParts = fullDomain.split('.'); + if (domainParts.length == 2) { + return fullDomain; + } + let rootDomain; + let continueSearching; + let startIndex = -2; + const TEST_COOKIE_NAME = `_rdc${Date.now()}`; + const TEST_COOKIE_VALUE = 'writeable'; + do { + rootDomain = domainParts.slice(startIndex).join('.'); + let expirationDate = new Date(utils.timestamp() + 10 * 1000).toUTCString(); + + // Write a test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + TEST_COOKIE_VALUE, + expirationDate, + 'Lax', + rootDomain, + undefined + ); + + // See if the write was successful + const value = coreStorage.getCookie(TEST_COOKIE_NAME, undefined); + if (value === TEST_COOKIE_VALUE) { + continueSearching = false; + // Delete our test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + '', + 'Thu, 01 Jan 1970 00:00:01 GMT', + undefined, + rootDomain, + undefined + ); + } else { + startIndex += -1; + continueSearching = Math.abs(startIndex) <= domainParts.length; + } + } while (continueSearching); + return rootDomain; +} + +/** + * Checks for referer and if exists merges into ortb2 global data + */ +function setReferer() { + if (getRefererInfo().referer) utils.mergeDeep(ortb2, { site: { ref: getRefererInfo().referer } }); +} + +/** + * Checks for canonical url and if exists merges into ortb2 global data + */ +function setPage() { + if (getRefererInfo().canonicalUrl) utils.mergeDeep(ortb2, { site: { page: getRefererInfo().canonicalUrl } }); +} + +/** + * Checks for canonical url and if exists retrieves domain and merges into ortb2 global data + */ +function setDomain() { + let parseDomain = function(url) { + if (!url || typeof url !== 'string' || url.length === 0) return; + + var match = url.match(/^(?:https?:\/\/)?(?:www\.)?(.*?(?=(\?|\#|\/|$)))/i); + + return match && match[1]; + }; + + let domain = parseDomain(getRefererInfo().canonicalUrl) + + if (domain) { + utils.mergeDeep(ortb2, { site: { domain: domain } }); + utils.mergeDeep(ortb2, { site: { publisher: { domain: findRootDomain(domain) } } }); + }; +} + +/** + * Checks for screen/device width and height and sets dimensions + */ +function setDimensions() { + let width; + let height; + + try { + width = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; + height = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + } catch (e) { + width = window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth; + height = window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight; + } + + utils.mergeDeep(ortb2, { device: { w: width, h: height } }); +} + +/** + * Scans page for meta keywords, and if exists, merges into site.keywords + */ +function setKeywords() { + let keywords; + + try { + keywords = win.document.querySelector("meta[name='keywords']"); + } catch (e) { + keywords = window.document.querySelector("meta[name='keywords']"); + } + + if (keywords && keywords.content) utils.mergeDeep(ortb2, { site: { keywords: keywords.content.replace(/\s/g, '') } }); +} + +/** + * Resets modules global ortb2 data + */ +const resetOrtb2 = () => { ortb2 = {} }; + +function runEnrichments() { + setReferer(); + setPage(); + setDomain(); + setDimensions(); + setKeywords(); + + return ortb2; +} + +/** + * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + */ +export function initSubmodule(fpdConf, data) { + resetOrtb2(); + + return (!fpdConf.skipEnrichments) ? utils.mergeDeep(runEnrichments(), data) : data; +} + +/** @type {firstPartyDataSubmodule} */ +export const enrichmentsSubmodule = { + name: 'enrichments', + queue: 2, + init: initSubmodule +} + +submodule('firstPartyData', enrichmentsSubmodule) diff --git a/modules/envivoBidAdapter.js b/modules/envivoBidAdapter.js deleted file mode 100644 index b9c80ffd468..00000000000 --- a/modules/envivoBidAdapter.js +++ /dev/null @@ -1,129 +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 = 'envivo'; -const DOMAIN = 'https://ad.nvivo.tv/'; -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 + 'ads/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) { - bidAd.adResponse = { - content: bidAd.vastXml, - height: bidAd.height, - width: bidAd.width - }; - bidAd.ttl = config.getConfig('_bidderTimeout') - bidAd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, { - id: bidAd.adUnitCode, - url: RENDERER_URL - }, bidAd.adUnitCode) : undefined; - bidResponses.push(bidAd); - }); - - 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, rendererParams, adUnitCode) { - const renderer = Renderer.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false, - config: {'player_height': bidAd.height, 'player_width': bidAd.width}, - 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 + 'ads/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 + 'ads/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/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index ac5ba659ad7..dd96353dea3 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -8,7 +9,7 @@ const BIDDER_CODE = 'eplanning'; const rnd = Math.random(); const DEFAULT_SV = 'ads.us.e-planning.net'; const DEFAULT_ISV = 'i.e-planning.net'; -const PARAMS = ['ci', 'sv', 't', 'ml']; +const PARAMS = ['ci', 'sv', 't', 'ml', 'sn']; const DOLLARS = 'USD'; const NET_REVENUE = true; const TTL = 120; @@ -16,6 +17,9 @@ const NULL_SIZE = '1x1'; const FILE = 'file'; const STORAGE_RENDER_PREFIX = 'pbsr_'; const STORAGE_VIEW_PREFIX = 'pbvi_'; +const mobileUserAgent = isMobileUserAgent(); +const PRIORITY_ORDER_FOR_MOBILE_SIZES_ASC = ['1x1', '300x50', '320x50', '300x250']; +const PRIORITY_ORDER_FOR_DESKTOP_SIZES_ASC = ['1x1', '970x90', '970x250', '160x600', '300x600', '728x90', '300x250']; export const spec = { code: BIDDER_CODE, @@ -43,7 +47,7 @@ export const spec = { url = 'https://' + urlConfig.isv + '/layers/t_pbjs_2.json'; params = {}; } else { - url = 'https://' + (urlConfig.sv || DEFAULT_SV) + '/hb/1/' + urlConfig.ci + '/' + dfpClientId + '/' + getDomain(pageUrl) + '/' + sec; + url = 'https://' + (urlConfig.sv || DEFAULT_SV) + '/pbjs/1/' + urlConfig.ci + '/' + dfpClientId + '/' + getDomain(pageUrl) + '/' + sec; const referrerUrl = bidderRequest.refererInfo.referer.reachedTop ? window.top.document.referrer : bidderRequest.refererInfo.referer; if (storage.hasLocalStorage()) { @@ -54,7 +58,6 @@ export const spec = { rnd: rnd, e: spaces.str, ur: pageUrl || FILE, - r: 'pbjs', pbv: '$prebid.version$', ncb: '1', vs: spaces.vs @@ -79,6 +82,13 @@ export const spec = { if (bidderRequest && bidderRequest.uspConsent) { params.ccpa = bidderRequest.uspConsent; } + + if ((getGlobal()).getUserIds && typeof (getGlobal()).getUserIds === 'function') { + const userIds = (getGlobal()).getUserIds(); + for (var id in userIds) { + params['e_' + id] = (typeof userIds[id] === 'object') ? encodeURIComponent(JSON.stringify(userIds[id])) : encodeURIComponent(userIds[id]); + } + } } return { @@ -107,6 +117,11 @@ export const spec = { netRevenue: NET_REVENUE, currency: DOLLARS, }; + if (ad.adom) { + bidResponse.meta = { + advertiserDomains: ad.adom + }; + } bidResponses.push(bidResponse); }); } @@ -140,6 +155,18 @@ export const spec = { }, } +function getUserAgent() { + return window.navigator.userAgent; +} +function getInnerWidth() { + return utils.getWindowSelf().innerWidth; +} +function isMobileUserAgent() { + return getUserAgent().match(/(mobile)|(ip(hone|ad))|(android)|(blackberry)|(nokia)|(phone)|(opera\smini)/i); +} +function isMobileDevice() { + return (getInnerWidth() <= 1024) || window.orientation || mobileUserAgent; +} function getUrlConfig(bidRequests) { if (isTestRequest(bidRequests)) { return getTestConfig(bidRequests.filter(br => br.params.t)); @@ -173,8 +200,32 @@ function getTestConfig(bidRequests) { }; } +function compareSizesByPriority(size1, size2) { + var priorityOrderForSizesAsc = isMobileDevice() ? PRIORITY_ORDER_FOR_MOBILE_SIZES_ASC : PRIORITY_ORDER_FOR_DESKTOP_SIZES_ASC; + var index1 = priorityOrderForSizesAsc.indexOf(size1); + var index2 = priorityOrderForSizesAsc.indexOf(size2); + if (index1 > -1) { + if (index2 > -1) { + return (index1 < index2) ? 1 : -1; + } else { + return -1; + } + } else { + return (index2 > -1) ? 1 : 0; + } +} + +function getSizesSortedByPriority(sizes) { + return utils.parseSizesInput(sizes).sort(compareSizesByPriority); +} + function getSize(bid, first) { - return bid.sizes && bid.sizes.length ? utils.parseSizesInput(first ? bid.sizes[0] : bid.sizes).join(',') : NULL_SIZE; + var arraySizes = bid.sizes && bid.sizes.length ? getSizesSortedByPriority(bid.sizes) : []; + if (arraySizes.length) { + return first ? arraySizes[0] : arraySizes.join(','); + } else { + return NULL_SIZE; + } } function getSpacesStruct(bids) { @@ -197,7 +248,14 @@ function getSpaces(bidRequests, ml) { let es = {str: '', vs: '', map: {}}; es.str = Object.keys(spacesStruct).map(size => spacesStruct[size].map((bid, i) => { es.vs += getVs(bid); - let name = ml ? cleanName(bid.adUnitCode) : getSize(bid, true) + '_' + i; + + let name; + if (ml) { + name = cleanName(bid.adUnitCode); + } else { + name = (bid.params && bid.params.sn) || (getSize(bid, true) + '_' + i); + } + es.map[name] = bid.bidId; return name + ':' + getSize(bid); }).join('+')).join('+'); diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index 5e07561044a..51f2759d256 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,5 +1,5 @@ -'use strict'; - +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -29,6 +29,7 @@ export const spec = { var request = []; var bids = JSON.parse(JSON.stringify(validBidRequests)); var lastCountry = 'sk'; + var floors = []; for (i = 0, l = bids.length; i < l; i++) { bid = bids[i]; if (countryMap[bid.params.country]) { @@ -37,18 +38,25 @@ export const spec = { reqParams = bid.params; reqParams.transactionId = bid.transactionId; request.push(formRequestUrl(reqParams)); + floors[i] = getBidFloor(bid); } request.unshift('https://' + lastCountry + '.search.etargetnet.com/hb/?hbget=1'); netRevenue = 'net'; - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - gdprObject = { - gdpr: bidderRequest.gdprConsent.gdprApplies, - gdpr_consent: bidderRequest.gdprConsent.consentString - }; - request.push('gdpr=' + gdprObject.gdpr); - request.push('gdpr_consent=' + gdprObject.gdpr_consent); + if (bidderRequest) { + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + gdprObject = { + gdpr: bidderRequest.gdprConsent.gdprApplies, + gdpr_consent: bidderRequest.gdprConsent.consentString + }; + request.push('gdpr=' + gdprObject.gdpr); + request.push('gdpr_consent=' + gdprObject.gdpr_consent); + } + bidderRequest.metaData = getMetaData(); + if (floors.length > 0) { + bidderRequest.floors = floors; + } } return { @@ -61,6 +69,35 @@ export const spec = { gdpr: gdprObject }; + function getMetaData() { + var mts = {}; + var hmetas = document.getElementsByTagName('meta'); + var wnames = ['title', 'og:title', 'description', 'og:description', 'og:url', 'base', 'keywords']; + try { + for (var k in hmetas) { + if (typeof hmetas[k] == 'object') { + var mname = hmetas[k].name || hmetas[k].getAttribute('property'); + var mcont = hmetas[k].content; + if (!!mname && mname != 'null' && !!mcont) { + if (wnames.indexOf(mname) >= 0) { + if (!mts[mname]) { + mts[mname] = []; + } + mts[mname].push(mcont); + } + } + } + } + mts['title'] = [(document.getElementsByTagName('title')[0] || []).innerHTML]; + mts['base'] = [(document.getElementsByTagName('base')[0] || {}).href]; + mts['referer'] = [document.location.href]; + mts['ortb2'] = (config.getConfig('ortb2') || {}); + } catch (e) { + mts.error = e; + } + return mts; + } + function formRequestUrl(reqData) { var key; var url = []; @@ -96,6 +133,7 @@ export const spec = { currency: data.win_cur, netRevenue: true, ttl: 360, + reason: data.reason ? data.reason : 'none', ad: data.banner, vastXml: data.vast_content, vastUrl: data.vast_link, @@ -106,9 +144,13 @@ export const spec = { bidObject.gdpr = bidRequest.gdpr.gdpr; bidObject.gdpr_consent = bidRequest.gdpr.gdpr_consent; } + if (bid.adomain) { + utils.deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); + } bidRespones.push(bidObject); } } + return bidRespones; function verifySize(adItem, validSizes) { @@ -122,4 +164,18 @@ export const spec = { } } }; +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return null; + } + let floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor)) { + return floor.floor; + } + return null; +} registerBidder(spec); diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js new file mode 100644 index 00000000000..bb838788f07 --- /dev/null +++ b/modules/fabrickIdSystem.js @@ -0,0 +1,183 @@ +/** + * This module adds neustar's fabrickId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/fabrickIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + +/** @type {Submodule} */ +export const fabrickIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'fabrickId', + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(value) { + if (value && value.fabrickId) { + return { 'fabrickId': value.fabrickId }; + } else { + return undefined; + } + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleConfig} [config] + * @param {ConsentData} + * @param {Object} cacheIdObj - existing id, if any consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData, cacheIdObj) { + try { + const configParams = (config && config.params) || {}; + if (window.fabrickMod1) { + window.fabrickMod1(configParams, consentData, cacheIdObj); + } + if (!configParams || !configParams.apiKey || typeof configParams.apiKey !== 'string') { + utils.logError('fabrick submodule requires an apiKey.'); + return; + } + try { + let url = _getBaseUrl(configParams); + let keysArr = Object.keys(configParams); + for (let i in keysArr) { + let k = keysArr[i]; + if (k === 'url' || k === 'refererInfo' || (k.length > 3 && k.substring(0, 3) === 'max')) { + continue; + } + let v = configParams[k]; + if (Array.isArray(v)) { + for (let j in v) { + if (typeof v[j] === 'string' || typeof v[j] === 'number') { + url += `${k}=${v[j]}&`; + } + } + } else if (typeof v === 'string' || typeof v === 'number') { + url += `${k}=${v}&`; + } + } + // pull off the trailing & + url = url.slice(0, -1) + const referer = _getRefererInfo(configParams); + const refs = new Map(); + _setReferrer(refs, referer.referer); + if (referer.stack && referer.stack[0]) { + _setReferrer(refs, referer.stack[0]); + } + _setReferrer(refs, referer.canonicalUrl); + _setReferrer(refs, window.location.href); + + refs.forEach(v => { + url = appendUrl(url, 'r', v, configParams); + }); + + const resp = function (callback) { + const callbacks = { + success: response => { + if (window.fabrickMod2) { + return window.fabrickMod2( + callback, response, configParams, consentData, cacheIdObj); + } else { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + responseObj = {}; + } + } + callback(responseObj); + } + }, + error: error => { + utils.logError(`fabrickId fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, null, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + } catch (e) { + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } catch (e) { + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } +}; + +function _getRefererInfo(configParams) { + if (configParams.refererInfo) { + return configParams.refererInfo; + } else { + return getRefererInfo(); + } +} + +function _getBaseUrl(configParams) { + if (configParams.url) { + return configParams.url; + } else { + return `https://fid.agkn.com/f?`; + } +} + +function _setReferrer(refs, s) { + if (s) { + // store the longest one for the same URI + const url = s.split('?')[0]; + // OR store the longest one for the same domain + // const url = s.split('?')[0].replace('http://','').replace('https://', '').split('/')[0]; + if (refs.has(url)) { + const prevRef = refs.get(url); + if (s.length > prevRef.length) { + refs.set(url, s); + } + } else { + refs.set(url, s); + } + } +} + +export function appendUrl(url, paramName, s, configParams) { + const maxUrlLen = (configParams && configParams.maxUrlLen) || 2000; + const maxRefLen = (configParams && configParams.maxRefLen) || 1000; + const maxSpaceAvailable = (configParams && configParams.maxSpaceAvailable) || 50; + // make sure we have enough space left to make it worthwhile + if (s && url.length < (maxUrlLen - maxSpaceAvailable)) { + let thisMaxRefLen = maxUrlLen - url.length; + if (thisMaxRefLen > maxRefLen) { + thisMaxRefLen = maxRefLen; + } + + s = `&${paramName}=${encodeURIComponent(s)}`; + + if (s.length >= thisMaxRefLen) { + s = s.substring(0, thisMaxRefLen); + if (s.charAt(s.length - 1) === '%') { + s = s.substring(0, thisMaxRefLen - 1); + } else if (s.charAt(s.length - 2) === '%') { + s = s.substring(0, thisMaxRefLen - 2); + } + } + return `${url}${s}` + } else { + return url; + } +} + +submodule('userId', fabrickIdSubmodule); diff --git a/modules/fabrickIdSystem.md b/modules/fabrickIdSystem.md new file mode 100644 index 00000000000..268c861710a --- /dev/null +++ b/modules/fabrickIdSystem.md @@ -0,0 +1,24 @@ +## Neustar Fabrick User ID Submodule + +Fabrick ID Module - https://www.home.neustar/fabrick +Product and Sales Inquiries: 1-855-898-0036 + +## Example configuration for publishers: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'fabrickId', + storage: { + name: 'pbjs_fabrickId', + type: 'cookie', + expires: 7 + }, + params: { + apiKey: 'your apiKey', // provided to you by Neustar + e: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66' // example hash identifier (sha256) + } + }] + } +}); +``` diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 3992f2db5e0..54a4ef0c998 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,13 +1,13 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; /** * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.0'; +const VERSION = '1.0.2'; /** * @typedef {object} FeedAdApiBidRequest @@ -61,6 +61,11 @@ const VERSION = '1.0.0'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * The IAB TCF 2.0 vendor ID for the FeedAd GmbH + */ +const TCF_VENDOR_ID = 781; + /** * Bidder network identity code * @type {string} @@ -71,7 +76,7 @@ const BIDDER_CODE = 'feedad'; * The media types supported by FeedAd * @type {MediaType[]} */ -const MEDIA_TYPES = [VIDEO, BANNER]; +const MEDIA_TYPES = [BANNER]; /** * Tag for logging @@ -204,6 +209,10 @@ function buildRequests(validBidRequests, bidderRequest) { referer: data.refererInfo.referer, transactionId: bid.transactionId }); + if (bidderRequest.gdprConsent) { + data.consentIabTcf = bidderRequest.gdprConsent.consentString; + data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; + } return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, @@ -279,6 +288,7 @@ function trackingHandlerFactory(klass) { */ export const spec = { code: BIDDER_CODE, + gvlid: TCF_VENDOR_ID, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, buildRequests, diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index fd57025c29e..6f705df36b5 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -18,9 +18,6 @@ Prebid.JS adapter that connects to the FeedAd demand sources. mediaTypes: { banner: { // supports all banner sizes sizes: [[300, 250]], - }, - video: { // supports only outstream video - context: 'outstream' } }, bids: [ diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js deleted file mode 100644 index baf5384fbfe..00000000000 --- a/modules/fidelityBidAdapter.js +++ /dev/null @@ -1,147 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'fidelity'; -const BIDDER_SERVER = 'x.fidelity-media.com'; -const FIDELITY_VENDOR_ID = 408; -export const spec = { - code: BIDDER_CODE, - aliases: ['kubient'], - gvlid: 408, - isBidRequestValid: function isBidRequestValid(bid) { - return !!(bid && bid.params && bid.params.zoneid); - }, - buildRequests: function buildRequests(validBidRequests, bidderRequest) { - return validBidRequests.map(bidRequest => { - var server = bidRequest.params.server || BIDDER_SERVER; - - const payload = { - from: 'hb', - v: '1.0', - requestid: bidRequest.bidderRequestId, - impid: bidRequest.bidId, - zoneid: bidRequest.params.zoneid, - floor: parseFloat(bidRequest.params.floor) > 0 ? bidRequest.params.floor : 0, - charset: document.charSet || document.characterSet, - subid: 'hb', - flashver: getFlashVersion(), - tmax: bidderRequest.timeout, - defloc: bidderRequest.refererInfo.referer, - referrer: getTopWindowReferrer(), - schain: getSupplyChain(bidRequest.schain), - }; - setConsentParams(bidderRequest.gdprConsent, bidderRequest.uspConsent, payload); - - return { - method: 'GET', - url: 'https://' + server + '/delivery/hb.php', - data: payload - }; - }); - }, - interpretResponse: function interpretResponse(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.width, - height: bid.height, - ad: bid.adm, - netRevenue: bid.netRevenue, - currency: bid.cur, - ttl: bid.ttl, - }; - - bidResponses.push(bidResponse); - })); - } - return bidResponses; - }, - getUserSyncs: function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (syncOptions.iframeEnabled) { - var url = 'https://' + BIDDER_SERVER + '/delivery/matches.php'; - var payload = { - type: 'iframe' - }; - setConsentParams(gdprConsent, uspConsent, payload); - - return [{ - type: 'iframe', - url: url + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, '') - }]; - } - } -} - -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 || ''; -} - -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } -} - -function setConsentParams(gdprConsent, uspConsent, payload) { - if (gdprConsent) { - payload.gdpr = 0; - payload.consent_str = ''; - payload.consent_given = 0; - if (typeof gdprConsent.gdprApplies !== 'undefined') { - payload.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - if (typeof gdprConsent.consentString !== 'undefined') { - payload.consent_str = gdprConsent.consentString; - } - if (gdprConsent.apiVersion === 1 && gdprConsent.vendorData && gdprConsent.vendorData.vendorConsents && typeof gdprConsent.vendorData.vendorConsents[FIDELITY_VENDOR_ID.toString(10)] !== 'undefined') { - payload.consent_given = gdprConsent.vendorData.vendorConsents[FIDELITY_VENDOR_ID.toString(10)] ? 1 : 0; - } - if (gdprConsent.apiVersion === 2 && gdprConsent.vendorData && gdprConsent.vendorData.vendor && gdprConsent.vendorData.vendor.consents && typeof gdprConsent.vendorData.vendor.consents[FIDELITY_VENDOR_ID.toString(10)] !== 'undefined') { - payload.consent_given = gdprConsent.vendorData.vendor.consents[FIDELITY_VENDOR_ID.toString(10)] ? 1 : 0; - } - } - if (typeof uspConsent !== 'undefined') { - payload.us_privacy = uspConsent; - } -} - -function getSupplyChain(schain) { - var supplyChain = ''; - if (schain != null && schain.nodes) { - supplyChain = schain.ver + ',' + schain.complete; - for (let i = 0; i < schain.nodes.length; i++) { - supplyChain += '!'; - supplyChain += (schain.nodes[i].asi) ? encodeURIComponent(schain.nodes[i].asi) : ''; - supplyChain += ','; - supplyChain += (schain.nodes[i].sid) ? encodeURIComponent(schain.nodes[i].sid) : ''; - supplyChain += ','; - supplyChain += (schain.nodes[i].hp) ? encodeURIComponent(schain.nodes[i].hp) : ''; - supplyChain += ','; - supplyChain += (schain.nodes[i].rid) ? encodeURIComponent(schain.nodes[i].rid) : ''; - supplyChain += ','; - supplyChain += (schain.nodes[i].name) ? encodeURIComponent(schain.nodes[i].name) : ''; - supplyChain += ','; - supplyChain += (schain.nodes[i].domain) ? encodeURIComponent(schain.nodes[i].domain) : ''; - } - } - return supplyChain; -} -registerBidder(spec); diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js new file mode 100644 index 00000000000..e4bd31e49df --- /dev/null +++ b/modules/flocIdSystem.js @@ -0,0 +1,110 @@ +/** + * This module adds flocId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/flocId + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js' + +const MODULE_NAME = 'flocId'; + +/** + * Add meta tag to support enabling of floc origin trial + * @function + * @param {string} token - configured token for origin-trial + */ +function enableOriginTrial(token) { + const tokenElement = document.createElement('meta'); + tokenElement.httpEquiv = 'origin-trial'; + tokenElement.content = token; + document.head.appendChild(tokenElement); +} + +/** + * Get the interest cohort. + * @param successCallback + * @param errorCallback + */ +function getFlocData(successCallback, errorCallback) { + document.interestCohort() + .then((data) => { + successCallback(data); + }).catch((error) => { + errorCallback(error); + }); +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + if (value) { + result.flocId = value; + utils.logInfo('Decoded value ' + JSON.stringify(result)); + return result; + } + return undefined; +} + +/** @type {Submodule} */ +export const flocIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{flocId:{ id: string }} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + /** + * If chrome and cohort enabled performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId(config) { + // Block usage of storage of cohort ID + const checkStorage = (config && config.storage); + if (checkStorage) { + utils.logError('User ID - flocId submodule storage should not defined'); + return; + } + // Validate feature is enabled + const isFlocEnabled = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime) && !!document.featurePolicy && !!document.featurePolicy.features() && document.featurePolicy.features().includes('interest-cohort'); + + if (isFlocEnabled) { + const configParams = (config && config.params) || {}; + if (configParams && (typeof configParams.token === 'string')) { + // Insert meta-tag with token from configuration + enableOriginTrial(configParams.token); + } + // Example expected output { "id": "14159", "version": "chrome.1.0" } + let returnCallback = (cb) => { + getFlocData((data) => { + returnCallback = () => { return data; } + utils.logInfo('Cohort id: ' + JSON.stringify(data)); + cb(data); + }, (err) => { + utils.logInfo(err); + cb(undefined); + }); + }; + + return {callback: returnCallback}; + } + } +}; + +submodule('userId', flocIdSubmodule); diff --git a/modules/flocIdSystem.md b/modules/flocIdSystem.md new file mode 100644 index 00000000000..07184700a14 --- /dev/null +++ b/modules/flocIdSystem.md @@ -0,0 +1,34 @@ +## FloC ID User ID Submodule + +### Building Prebid with Floc Id Support +Your Prebid build must include the modules for both **userId** and **flocIdSystem** 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,flocIdSystem + +### Prebid Params + +Individual params may be set for the FloC ID User ID Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'flocId', + params: { + token: "Registered token or default sharedid.org token" + } + }] + } +}); +``` + +### Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the FloC ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Floc ID module - `"flocId"` | `"flocId"` | +| params | Optional | Object | Details for flocId syncing. | | +| params.token | Optional | Object | Publisher registered token.To get new token, register https://developer.chrome.com/origintrials/#/trials/active for Federated Learning of Cohorts. Default sharedid.org token: token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9"| token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" + | +| storage | Not Allowed | Object | Will ask browser for cohort everytime. Setting storage will fail id lookup || diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js deleted file mode 100644 index 420fe04ddcb..00000000000 --- a/modules/fluctBidAdapter.js +++ /dev/null @@ -1,121 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'fluct'; -const END_POINT = 'https://hb.adingo.jp/prebid'; -const VERSION = '1.2'; -const NET_REVENUE = true; -const TTL = 300; - -export const spec = { - code: BIDDER_CODE, - aliases: ['adingo'], - - /** - * 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 !!(bid.params.groupId && 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: (validBidRequests, bidderRequest) => { - const serverRequests = []; - const referer = bidderRequest.refererInfo.referer; - - utils._each(validBidRequests, (request) => { - const data = Object(); - - data.referer = referer; - data.adUnitCode = request.adUnitCode; - data.bidId = request.bidId; - data.transactionId = request.transactionId; - - data.sizes = []; - utils._each(request.sizes, (size) => { - data.sizes.push({ - w: size[0], - h: size[1] - }); - }); - - data.params = request.params; - - serverRequests.push({ - method: 'POST', - url: END_POINT, - options: { - contentType: 'application/json', - withCredentials: true, - customHeaders: { - 'x-fluct-app': 'prebid/fluctBidAdapter', - 'x-fluct-version': VERSION, - 'x-openrtb-version': 2.5 - } - }, - data: data - }); - }); - - return serverRequests; - }, - - /* - * Unpack the respnse from the server into a list of bids. - * - * @param {serverResponse} serverResponse A successful response from the server. - * @return {bid[]} An array of bids which weer nested inside the server. - */ - interpretResponse: (serverResponse, serverRequest) => { - const bidResponses = []; - - const res = serverResponse.body; - if (!utils.isEmpty(res) && !utils.isEmpty(res.seatbid) && !utils.isEmpty(res.seatbid[0].bid)) { - const bid = res.seatbid[0].bid[0]; - const dealId = bid.dealid; - const beaconUrl = bid.burl; - const callImpBeacon = ``; - let data = { - bidderCode: BIDDER_CODE, - requestId: res.id, - currency: res.cur, - cpm: parseFloat(bid.price) || 0, - netRevenue: NET_REVENUE, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - ttl: TTL, - ad: bid.adm + callImpBeacon, - }; - if (!utils.isEmpty(dealId)) { - data.dealId = dealId; - } - bidResponses.push(data); - } - return bidResponses; - }, - - /* - * Register the user sync pixels which should be dropped after the auction. - * - * @params {syncOptions} syncOptions which user syncs are allowed? - * @params {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - * - */ - getUserSyncs: (syncOptions, serverResponses) => { - return []; - }, -}; - -registerBidder(spec); diff --git a/modules/fpdModule/index.js b/modules/fpdModule/index.js new file mode 100644 index 00000000000..427547a4e4d --- /dev/null +++ b/modules/fpdModule/index.js @@ -0,0 +1,58 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import { config } from '../../src/config.js'; +import { module, getHook } from '../../src/hook.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; +import { addBidderRequests } from '../../src/auction.js'; + +let submodules = []; + +/** + * enable submodule in User ID + * @param {RtdSubmodule} submodule + */ +export function registerSubmodules(submodule) { + submodules.push(submodule); +} + +export function init() { + let modConf = config.getConfig('firstPartyData') || {}; + let ortb2 = config.getConfig('ortb2') || {}; + + submodules.sort((a, b) => { + return ((a.queue || 1) - (b.queue || 1)); + }).forEach(submodule => { + ortb2 = submodule.init(modConf, ortb2); + }); + + config.setConfig({ortb2}); +} + +/** + * BidderRequests hook to intiate module and reset modules ortb2 data object + */ +function addBidderRequestHook(fn, bidderRequests) { + init(); + fn.call(this, bidderRequests); + // Removes hook after run + addBidderRequests.getHooks({ hook: addBidderRequestHook }).remove(); +} + +/** + * Sets bidderRequests hook + */ +function setupHook() { + getHook('addBidderRequests').before(addBidderRequestHook); +} + +module('firstPartyData', registerSubmodules); + +// Runs setupHook on initial load +setupHook(); + +/** + * Global function to reinitiate module + */ +(getGlobal()).refreshFpd = setupHook; diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md new file mode 100644 index 00000000000..638c966883a --- /dev/null +++ b/modules/fpdModule/index.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: First Party Data Module +``` + +# Description + +Module to perform the following functions to allow for consistent set of first party data using the following submodules. + +Enrichment Submodule: +- populate available data into object: referer, meta-keywords, cur + +Validation Submodule: +- verify OpenRTB datatypes, remove/warn any that are likely to choke downstream readers +- verify that certain OpenRTB attributes are not specified +- optionally suppress user FPD based on the existence of _pubcid_optout + + +1. Module initializes on first load and set bidRequestHook +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +3. After hook complete, it is disabled - meaning module only runs on first auction +4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load + + +This module will automatically run first party data enrichments and validations dependant on which submodules are included. There is no configuration required. In order to load the module and submodule(s) and opt out of either enrichements or validations, use the below opt out configuration + +# Module Control Configuration + +``` + +pbjs.setConfig({ + firstPartyData: { + skipValidations: true, // default to false + skipEnrichments: true // default to false + } +}); + +``` + +# Requirements + +At least one of the submodules must be included in order to successfully run the corresponding above operations. + +enrichmentFpdModule +validationFpdModule \ No newline at end of file diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 5e3717ce1fe..fa2f7b4cd6b 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -72,7 +72,7 @@ function getPricing(xmlNode) { price: priceNode.textContent || priceNode.innerText }; } else { - utils.logWarn('PREBID - ' + BIDDER_CODE + ': Can\'t get pricing data. Is price awareness enabled?'); + utils.logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); } return princingData; @@ -102,22 +102,49 @@ function getCreativeId(xmlNode) { return creaId; } -function getDealId(xmlNode) { - var dealId = ''; +function getValueFromKeyInImpressionNode(xmlNode, key) { + var value = ''; var impNodes = xmlNode.querySelectorAll('Impression'); // Nodelist.forEach is not supported in IE and Edge - // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ - + var isRootViewKeyPresent = false; + var isAdsDisplayStartedPresent = false; Array.prototype.forEach.call(impNodes, function (el) { - var queries = el.textContent.substring(el.textContent.indexOf('?') + 1).split('&'); + if (isRootViewKeyPresent && isAdsDisplayStartedPresent) { + return value; + } + isRootViewKeyPresent = false; + isAdsDisplayStartedPresent = false; + var text = el.textContent; + var queries = text.substring(el.textContent.indexOf('?') + 1).split('&'); + var tempValue = ''; Array.prototype.forEach.call(queries, function (item) { var split = item.split('='); - if (split[0] == 'dealId') { - dealId = split[1]; + if (split[0] == key) { + tempValue = split[1]; + } + if (split[0] == 'reqType' && split[1] == 'AdsDisplayStarted') { + isAdsDisplayStartedPresent = true; + } + if (split[0] == 'rootViewKey') { + isRootViewKeyPresent = true; } }); + if (isAdsDisplayStartedPresent) { + value = tempValue; + } }); + return value; +} + +function getDealId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'dealId'); +} + +function getBannerId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'adId'); +} - return dealId; +function getCampaignId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'campaignId'); } /** @@ -373,7 +400,8 @@ export const spec = { const princingData = getPricing(xmlDoc); const creativeId = getCreativeId(xmlDoc); const dealId = getDealId(xmlDoc); - + const campaignId = getCampaignId(xmlDoc); + const bannerId = getBannerId(xmlDoc); const topWin = getTopMostWindow(); if (!topWin.freewheelssp_cache) { topWin.freewheelssp_cache = {}; @@ -392,7 +420,10 @@ export const spec = { currency: princingData.currency, netRevenue: true, ttl: 360, - dealId: dealId + meta: { advertiserDomains: princingData.adomain && utils.isArray(princingData.adomain) ? princingData.adomain : [] }, + dealId: dealId, + campaignId: campaignId, + bannerId: bannerId }; if (bidrequest.mediaTypes.video) { @@ -407,16 +438,25 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { + var gdprParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } + if (syncOptions && syncOptions.pixelEnabled) { return [{ type: 'image', - url: USER_SYNC_URL + url: USER_SYNC_URL + gdprParams }]; } else { return []; } }, +}; -} registerBidder(spec); diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 5fd3c56b2c6..3e1298b7e23 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -84,7 +84,10 @@ function newBid(serverBid) { mediaType: serverBid.type, netRevenue: true, requestId: serverBid.id, - ttl: serverBid.seatbid[0].bid[0].ttl || 300 + ttl: serverBid.seatbid[0].bid[0].ttl || 300, + meta: { + advertiserDomains: serverBid.seatbid[0].bid[0].adomain && serverBid.seatbid[0].bid[0].adomain.length ? serverBid.seatbid[0].bid[0].adomain : [] + } }; if (serverBid.type == 'video') { diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 1316d74e430..68233ce7814 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; const ENDPOINTS = { 'gamoshi': 'https://rtb.gamoshi.io' @@ -37,12 +38,29 @@ export const helper = { } } return BANNER; + }, + getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return bid.params.bidfloor ? bid.params.bidfloor : null; + } + + let bidFloor = bid.getFloor({ + mediaType: '*', + size: '*', + currency: 'USD' + }); + + if (utils.isPlainObject(bidFloor) && !isNaN(bidFloor.floor) && bidFloor.currency === 'USD') { + return bidFloor.floor; + } + + return null; } }; export const spec = { code: 'gamoshi', - aliases: ['gambid', 'cleanmedia', '9MediaOnline'], + aliases: ['gambid', '9MediaOnline'], supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { @@ -105,13 +123,13 @@ export const spec = { id: transactionId, instl: params.instl === 1 ? 1 : 0, tagid: adUnitCode, - bidfloor: params.bidfloor || 0, + bidfloor: helper.getBidFloor(bidRequest) || 0, bidfloorcur: 'USD', secure: 1 }; const hasFavoredMediaType = - params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType); + params.favoredMediaType && includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { @@ -132,11 +150,19 @@ export const spec = { const playerSize = mediaTypes.video.playerSize || sizes; const videoImp = Object.assign({}, imp, { video: { - protocols: params.protocols || [1, 2, 3, 4, 5, 6], + protocols: bidRequest.mediaTypes.video.protocols || params.protocols || [1, 2, 3, 4, 5, 6], pos: params.pos || 0, ext: { context: mediaTypes.video.context - } + }, + mimes: bidRequest.mediaTypes.video.mimes, + maxduration: bidRequest.mediaTypes.video.maxduration, + api: bidRequest.mediaTypes.video.api, + skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, + placement: bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement, + minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, + playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, + startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay } }); @@ -157,7 +183,7 @@ export const spec = { let eids = []; if (bidRequest && bidRequest.userId) { - addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID'); addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); } if (eids.length > 0) { @@ -197,9 +223,15 @@ export const spec = { creativeId: bid.crid || bid.adid, netRevenue: true, currency: bid.cur || response.cur, - mediaType: helper.getMediaType(bid) + mediaType: helper.getMediaType(bid), }; + if (bid.adomain && bid.adomain.length) { + outBid.meta = { + advertiserDomains: bid.adomain + } + } + if (utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { if (outBid.mediaType === BANNER) { outBids.push(Object.assign({}, outBid, {ad: bid.adm})); diff --git a/modules/gamoshiBidAdapter.md b/modules/gamoshiBidAdapter.md index 6e930375059..49b727cecae 100644 --- a/modules/gamoshiBidAdapter.md +++ b/modules/gamoshiBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Gamoshi Bid Adapter Module Type: Bidder Adapter -Maintainer: salomon@gamoshi.com +Maintainer: dev@gamoshi.com ``` # Description diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 6a3fbdce1f2..02a2da3a7a4 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -11,39 +11,151 @@ import includes from 'core-js-pure/features/array/includes.js'; import { registerSyncInner } from '../src/adapters/bidderFactory.js'; import { getHook } from '../src/hook.js'; import { validateStorageEnforcement } from '../src/storageManager.js'; +import events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; -const purpose1 = 'storage'; +const TCF2 = { + 'purpose1': { id: 1, name: 'storage' }, + 'purpose2': { id: 2, name: 'basicAds' }, + 'purpose7': { id: 7, name: 'measurement' } +} + +/* + These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. +*/ +const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}]; + +export let purpose1Rule; +export let purpose2Rule; +export let purpose7Rule; + +export let enforcementRules; + +const storageBlocked = []; +const biddersBlocked = []; +const analyticsBlocked = []; let addedDeviceAccessHook = false; -let enforcementRules; -function getGvlid() { - let gvlid; - const bidderCode = config.getCurrentBidder(); +// Helps in stubbing these functions in unit tests. +export const internal = { + getGvlidForBidAdapter, + getGvlidForUserIdModule, + getGvlidForAnalyticsAdapter +}; + +/** + * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter. + * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter, + * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID + * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it + * without going to the next check. + * @param {{string|Object}} - module + * @return {number} - GVL ID + */ +export function getGvlid(module) { + let gvlid = null; + if (module) { + // Check user defined GVL Mapping in pbjs.setConfig() + const gvlMapping = config.getConfig('gvlMapping'); + + // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code + const moduleCode = typeof module === 'string' ? module : module.name; + + // Return GVL ID from user defined gvlMapping + if (gvlMapping && gvlMapping[moduleCode]) { + gvlid = gvlMapping[moduleCode]; + return gvlid; + } + + gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode); + } + return gvlid; +} + +/** + * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'. + * @param {string=} bidderCode - The 'code' property of the Bidder spec. + * @return {number} GVL ID + */ +function getGvlidForBidAdapter(bidderCode) { + let gvlid = null; + bidderCode = bidderCode || config.getCurrentBidder(); if (bidderCode) { const bidder = adapterManager.getBidAdapter(bidderCode); - gvlid = bidder.getSpec().gvlid; - } else { - utils.logWarn('Current module not found'); + if (bidder && bidder.getSpec) { + gvlid = bidder.getSpec().gvlid; + } } return gvlid; } /** - * This function takes in rules and consentData as input and validates against the consentData provided. If it returns true Prebid will allow the next call else it will log a warning - * @param {Object} rules enforcement rules set in config - * @param {Object} consentData gdpr consent data + * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'. + * @param {Object} userIdModule + * @return {number} GVL ID + */ +function getGvlidForUserIdModule(userIdModule) { + return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); +} + +/** + * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. + * @param {string} code - 'provider' property on the analytics adapter config + * @return {number} GVL ID + */ +function getGvlidForAnalyticsAdapter(code) { + return adapterManager.getAnalyticsAdapter(code) && (adapterManager.getAnalyticsAdapter(code).gvlid || null); +} + +/** + * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns, + * the caller may decide to suppress a TCF-sensitive activity. + * @param {Object} rule - enforcement rules set in config + * @param {Object} consentData - gdpr consent data + * @param {string=} currentModule - Bidder code of the current module + * @param {number=} gvlId - GVL ID for the module * @returns {boolean} */ -function validateRules(rule, consentData, currentModule, gvlid) { - // if vendor has exception => always true +export function validateRules(rule, consentData, currentModule, gvlId) { + const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; + + // return 'true' if vendor present in 'vendorExceptions' if (includes(rule.vendorExceptions || [], currentModule)) { return true; } - // if enforcePurpose is false or purpose was granted isAllowed is true, otherwise false - const purposeAllowed = rule.enforcePurpose === false || utils.deepAccess(consentData, 'vendorData.purpose.consents.1') === true; - // if enforceVendor is false or vendor was granted isAllowed is true, otherwise false - const vendorAllowed = rule.enforceVendor === false || utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlid}`) === true; + + // get data from the consent string + const purposeConsent = utils.deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); + const vendorConsent = utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + const liTransparency = utils.deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); + + /* + Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced + or the user has consented. Similar with vendors. + */ + const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; + const vendorAllowed = rule.enforceVendor === false || vendorConsent === true; + + /* + Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming + LI for Basic Ads (Purpose 2). Prebid.js can't check to see who's declaring what legal basis, so if LI has been + established for Purpose 2, allow the auction to take place and let the server sort out the legal basis calculation. + */ + if (purposeId === 2) { + return (purposeAllowed && vendorAllowed) || (liTransparency === true); + } + return purposeAllowed && vendorAllowed; } @@ -65,22 +177,26 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) { const consentData = gdprDataHandler.getConsentData(); if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { - if (!gvlid) { - gvlid = getGvlid(); + const curBidder = config.getCurrentBidder(); + // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder + if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { + gvlid = getGvlid(curBidder); + } else { + gvlid = getGvlid(moduleName) || gvlid; } - const curModule = moduleName || config.getCurrentBidder(); - const purpose1Rule = find(enforcementRules, hasPurpose1); + const curModule = moduleName || curBidder; let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid); if (isAllowed) { result.valid = true; fn.call(this, gvlid, moduleName, result); } else { - utils.logWarn(`User denied Permission for Device access for ${curModule}`); + curModule && utils.logWarn(`TCF2 denied device access for ${curModule}`); result.valid = false; + storageBlocked.push(curModule); fn.call(this, gvlid, moduleName, result); } } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings result.valid = true; fn.call(this, gvlid, moduleName, result); } @@ -100,21 +216,17 @@ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { - const gvlid = getGvlid(); const curBidder = config.getCurrentBidder(); - if (gvlid) { - const purpose1Rule = find(enforcementRules, hasPurpose1); - let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); - if (isAllowed) { - fn.call(this, ...args); - } else { - utils.logWarn(`User sync not allowed for ${curBidder}`); - } + const gvlid = getGvlid(curBidder); + let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); + if (isAllowed) { + fn.call(this, ...args); } else { utils.logWarn(`User sync not allowed for ${curBidder}`); + storageBlocked.push(curBidder); } } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings fn.call(this, ...args); } } else { @@ -132,24 +244,20 @@ export function userIdHook(fn, submodules, consentData) { if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { let userIdModules = submodules.map((submodule) => { - const gvlid = submodule.submodule.gvlid; + const gvlid = getGvlid(submodule.submodule); const moduleName = submodule.submodule.name; - if (gvlid) { - const purpose1Rule = find(enforcementRules, hasPurpose1); - let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); - if (isAllowed) { - return submodule; - } else { - utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); - } + let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); + if (isAllowed) { + return submodule; } else { utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); + storageBlocked.push(moduleName); } return undefined; }).filter(module => module) - fn.call(this, userIdModules, {...consentData, hasValidated: true}); + fn.call(this, userIdModules, { ...consentData, hasValidated: true }); } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings fn.call(this, submodules, consentData); } } else { @@ -157,28 +265,137 @@ export function userIdHook(fn, submodules, consentData) { } } -const hasPurpose1 = (rule) => { return rule.purpose === purpose1 } +/** + * Checks if bidders are allowed in the auction. + * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} adUnits + */ +export function makeBidRequestsHook(fn, adUnits, ...args) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + adUnits.forEach(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + const currBidder = bid.bidder; + const gvlId = getGvlid(currBidder); + if (includes(biddersBlocked, currBidder)) return false; + const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); + if (!isAllowed) { + utils.logWarn(`TCF2 blocked auction for ${currBidder}`); + biddersBlocked.push(currBidder); + } + return isAllowed; + }); + }); + fn.call(this, adUnits, ...args); + } else { + // The module doesn't enforce TCF1.1 strings + fn.call(this, adUnits, ...args); + } + } else { + fn.call(this, adUnits, ...args); + } +} + +/** + * Checks if Analytics adapters are allowed to send data to their servers for furhter processing. + * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} config - Configuration object passed to pbjs.enableAnalytics() + */ +export function enableAnalyticsHook(fn, config) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + if (!utils.isArray(config)) { + config = [config] + } + config = config.filter(conf => { + const analyticsAdapterCode = conf.provider; + const gvlid = getGvlid(analyticsAdapterCode); + const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); + if (!isAllowed) { + analyticsBlocked.push(analyticsAdapterCode); + utils.logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); + } + return isAllowed; + }); + fn.call(this, config); + } else { + // This module doesn't enforce TCF1.1 strings + fn.call(this, config); + } + } else { + fn.call(this, config); + } +} + +/** + * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. + */ +function emitTCF2FinalResults() { + // remove null and duplicate values + const formatArray = function (arr) { + return arr.filter((i, k) => i !== null && arr.indexOf(i) === k); + } + const tcf2FinalResults = { + storageBlocked: formatArray(storageBlocked), + biddersBlocked: formatArray(biddersBlocked), + analyticsBlocked: formatArray(analyticsBlocked) + }; + + events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); +} + +events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); + +/* + Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). +*/ +const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name } +const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name } +const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name } /** - * A configuration function that initializes some module variables, as well as add hooks - * @param {Object} config GDPR enforcement config object + * A configuration function that initializes some module variables, as well as adds hooks + * @param {Object} config - GDPR enforcement config object */ export function setEnforcementConfig(config) { const rules = utils.deepAccess(config, 'gdpr.rules'); if (!rules) { - utils.logWarn('GDPR enforcement rules not defined, exiting enforcement module'); - return; + utils.logWarn('TCF2: enforcing P1 and P2 by default'); + enforcementRules = DEFAULT_RULES; + } else { + enforcementRules = rules; + } + + purpose1Rule = find(enforcementRules, hasPurpose1); + purpose2Rule = find(enforcementRules, hasPurpose2); + purpose7Rule = find(enforcementRules, hasPurpose7); + + if (!purpose1Rule) { + purpose1Rule = DEFAULT_RULES[0]; } - enforcementRules = rules; - const hasDefinedPurpose1 = find(enforcementRules, hasPurpose1); - if (hasDefinedPurpose1 && !addedDeviceAccessHook) { + if (!purpose2Rule) { + purpose2Rule = DEFAULT_RULES[1]; + } + + if (purpose1Rule && !addedDeviceAccessHook) { addedDeviceAccessHook = true; validateStorageEnforcement.before(deviceAccessHook, 49); registerSyncInner.before(userSyncHook, 48); // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build getHook('validateGdprEnforcement').before(userIdHook, 47); } + if (purpose2Rule) { + getHook('makeBidRequests').before(makeBidRequestsHook); + } + + if (purpose7Rule) { + getHook('enableAnalyticsCb').before(enableAnalyticsHook); + } } config.getConfig('consentManagement', config => setEnforcementConfig(config.consentManagement)); diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js new file mode 100644 index 00000000000..001ef67b66a --- /dev/null +++ b/modules/geoedgeRtdProvider.js @@ -0,0 +1,213 @@ +/** + * This module adds geoedge provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch creative wrapper from geoedge server + * The module will place geoedge RUM client on bid responses markup + * @module modules/geoedgeProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} key + * @property {?Object} bidders + * @property {?boolean} wap + * @property {?string} keyName + */ + +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js'; + +/** @type {string} */ +const SUBMODULE_NAME = 'geoedge'; +/** @type {string} */ +export const WRAPPER_URL = 'https://wrappers.geoedge.be/wrapper.html'; +/** @type {string} */ +/* eslint-disable no-template-curly-in-string */ +export const HTML_PLACEHOLDER = '${creative}'; +/** @type {string} */ +const PV_ID = generateUUID(); +/** @type {string} */ +const HOST_NAME = 'https://rumcdn.geoedge.be'; +/** @type {string} */ +const FILE_NAME = 'grumi.js'; +/** @type {function} */ +export let getClientUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME}`; +/** @type {string} */ +export let wrapper +/** @type {boolean} */; +let wrapperReady; +/** @type {boolean} */; +let preloaded; + +/** + * fetches the creative wrapper + * @param {function} sucess - success callback + */ +export function fetchWrapper(success) { + if (wrapperReady) { + return success(wrapper); + } + ajax(WRAPPER_URL, success); +} + +/** + * sets the wrapper and calls preload client + * @param {string} responseText + */ +export function setWrapper(responseText) { + wrapperReady = true; + wrapper = responseText; +} + +/** + * preloads the client + * @param {string} key + */ +export function preloadClient(key) { + let link = document.createElement('link'); + link.rel = 'preload'; + link.as = 'script'; + link.href = getClientUrl(key); + link.onload = () => { preloaded = true }; + insertElement(link); +} + +/** + * creates identity function for string replace without special replacement patterns + * @param {string} str + * @return {function} + */ +function replacer(str) { + return function () { + return str; + } +} + +export function wrapHtml(wrapper, html) { + return wrapper.replace(HTML_PLACEHOLDER, replacer(html)); +} + +/** + * generate macros dictionary from bid response + * @param {Object} bid + * @param {string} key + * @return {Object} + */ +function getMacros(bid, key) { + return { + '${key}': key, + '%%ADUNIT%%': bid.adUnitCode, + '%%WIDTH%%': bid.width, + '%%HEIGHT%%': bid.height, + '%%PATTERN:hb_adid%%': bid.adId, + '%%PATTERN:hb_bidder%%': bid.bidderCode, + '%_isHb!': true, + '%_hbcid!': bid.creativeId || '', + '%%PATTERN:hb_pb%%': bid.pbHg, + '%%SITE%%': location.hostname, + '%_pimp%': PV_ID + }; +} + +/** + * replace macro placeholders in a string with values from a dictionary + * @param {string} wrapper + * @param {Object} macros + * @return {string} + */ +function replaceMacros(wrapper, macros) { + var re = new RegExp('\\' + Object.keys(macros).join('|'), 'gi'); + + return wrapper.replace(re, function(matched) { + return macros[matched]; + }); +} + +/** + * build final creative html with creative wrapper + * @param {Object} bid + * @param {string} wrapper + * @param {string} html + * @return {string} + */ +function buildHtml(bid, wrapper, html, key) { + let macros = getMacros(bid, key); + wrapper = replaceMacros(wrapper, macros); + return wrapHtml(wrapper, html); +} + +/** + * muatates the bid ad property + * @param {Object} bid + * @param {string} ad + */ +function mutateBid(bid, ad) { + bid.ad = ad; +} + +/** + * wraps a bid object with the creative wrapper + * @param {Object} bid + * @param {string} key + */ +export function wrapBidResponse(bid, key) { + let wrapped = buildHtml(bid, wrapper, bid.ad, key); + mutateBid(bid, wrapped); +} + +/** + * checks if bidder's bids should be monitored + * @param {string} bidder + * @return {boolean} + */ +function isSupportedBidder(bidder, paramsBidders) { + return isEmpty(paramsBidders) || paramsBidders[bidder] === true; +} + +/** + * checks if bid should be monitored + * @param {Object} bid + * @return {boolean} + */ +function shouldWrap(bid, params) { + let supportedBidder = isSupportedBidder(bid.bidderCode, params.bidders); + let donePreload = params.wap ? preloaded : true; + return wrapperReady && supportedBidder && donePreload; +} + +function conditionallyWrap(bidResponse, config, userConsent) { + let params = config.params; + if (shouldWrap(bidResponse, params)) { + wrapBidResponse(bidResponse, params.key); + } +} + +function init(config, userConsent) { + let params = config.params; + if (!params || !params.key) { + logError('missing key for geoedge RTD module provider'); + return false; + } + preloadClient(params.key); + return true; +} + +/** @type {RtdSubmodule} */ +export const geoedgeSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init, + onBidResponseEvent: conditionallyWrap +}; + +export function beforeInit() { + fetchWrapper(setWrapper); + submodule('realTimeData', geoedgeSubmodule); +} + +beforeInit(); diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md new file mode 100644 index 00000000000..5414606612c --- /dev/null +++ b/modules/geoedgeRtdProvider.md @@ -0,0 +1,67 @@ +## Overview + +Module Name: Geoedge Rtd provider +Module Type: Rtd Provider +Maintainer: guy.books@geoedge.com + +The Geoedge Realtime module lets publishers block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. + +## Integration + +1) Build the geoedge RTD module into the Prebid.js package with: + +``` +gulp build --modules=geoedgeRtdProvider,... +``` + +2) Use `setConfig` to instruct Prebid.js to initilize the geoedge module, as specified below. + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'geoedge', + params: { + key: '123123', + bidders: { + 'bidderA': true, // monitor bids form this bidder + 'bidderB': false // do not monitor bids form this bidder. + }, + wap: true + } + }] + } +}); +``` + +Parameters details: + +{: .table .table-bordered .table-striped } +|Name |Type |Description |Notes | +| :------------ | :------------ | :------------ |:------------ | +|name | String | Real time data module name |Required, always 'geoedge' | +|params | Object | | | +|params.key | String | Customer key |Required, contact Geoedge to get your key | +|params.bidders | Object | Bidders to monitor |Optional, list of bidder to include / exclude from monitoring. Omitting this will monitor bids from all bidders. | +|params.wap |Boolean |Wrap after preload |Optional, defaults to `false`. Set to `true` if you want to monitor only after the module has preloaded the monitoring client. | + +## Example + +To view an integration example: + +1) in your cli run: + +``` +gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider +``` + +2) in your browser, navigate to: + +``` +http://localhost:9999/integrationExamples/gpt/geoedgeRtdProvider_example.html +``` diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 134dd0b4fc9..9127bc4be6e 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,5 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { isInteger } from '../src/utils.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'getintent'; const IS_NET_REVENUE = true; @@ -7,11 +8,25 @@ const BID_HOST = 'px.adhigh.net'; const BID_BANNER_PATH = '/rtb/direct_banner'; const BID_VIDEO_PATH = '/rtb/direct_vast'; const BID_RESPONSE_TTL_SEC = 360; -const VIDEO_PROPERTIES = [ - 'protocols', 'mimes', 'min_dur', 'max_dur', 'min_btr', 'max_btr', 'vi_format', 'api', 'skippable' -]; +const FLOOR_PARAM = 'floor'; +const CURRENCY_PARAM = 'cur'; +const DEFAULT_CURRENCY = 'RUB'; +const VIDEO_PROPERTIES = { + 'protocols': 'protocols', + 'mimes': 'mimes', + 'min_dur': 'minduration', + 'max_dur': 'maxduration', + 'min_btr': 'minbitrate', + 'max_btr': 'maxbitrate', + 'vi_format': null, + 'api': 'api', + 'skippable': 'skip', +}; +const SKIPPABLE_ALLOW = 'ALLOW'; +const SKIPPABLE_NOT_ALLOW = 'NOT_ALLOW'; + const OPTIONAL_PROPERTIES = [ - 'cur', 'floor', 'sid' + 'sid' ]; export const spec = { @@ -66,7 +81,10 @@ export const spec = { creativeId: responseBody.creative_id, cpm: responseBody.cpm, width: size[0], - height: size[1] + height: size[1], + meta: { + advertiserDomains: responseBody.adomain || [], + } }; if (responseBody.vast_url) { bid.mediaType = 'video'; @@ -105,19 +123,61 @@ function buildGiBidRequest(bidRequest) { if (bidRequest.sizes) { giBidRequest.size = produceSize(bidRequest.sizes); } - addVideo(bidRequest.params.video, giBidRequest); + + const currency = utils.getBidIdParameter(CURRENCY_PARAM, bidRequest.params); + const floorInfo = getBidFloor(bidRequest, currency); + if (floorInfo.floor) { + giBidRequest[FLOOR_PARAM] = floorInfo.floor; + } + if (floorInfo.currency) { + giBidRequest[CURRENCY_PARAM] = floorInfo.currency; + } + + if (giBidRequest.is_video) { + addVideo(bidRequest.params.video, bidRequest.mediaTypes.video, giBidRequest); + } addOptional(bidRequest.params, giBidRequest, OPTIONAL_PROPERTIES); return giBidRequest; } -function addVideo(video, giBidRequest) { - if (giBidRequest.is_video && video) { - for (let i = 0, l = VIDEO_PROPERTIES.length; i < l; i++) { - let key = VIDEO_PROPERTIES[i]; - if (video.hasOwnProperty(key)) { - giBidRequest[key] = Array.isArray(video[key]) ? video[key].join(',') : video[key]; +function getBidFloor(bidRequest, currency) { + let floorInfo = {}; + + if (utils.isFn(bidRequest.getFloor)) { + floorInfo = bidRequest.getFloor({ + currency: currency || DEFAULT_CURRENCY, + mediaType: bidRequest.mediaType, + size: bidRequest.sizes || '*' + }); + } + + return { + floor: floorInfo.floor || bidRequest.params[FLOOR_PARAM] || 0, + currency: floorInfo.currency || currency || '', + }; +} + +function addVideo(videoParams, mediaTypesVideoParams, giBidRequest) { + videoParams = videoParams || {}; + mediaTypesVideoParams = mediaTypesVideoParams || {}; + + for (let videoParam in VIDEO_PROPERTIES) { + let paramValue; + + const mediaTypesVideoParam = VIDEO_PROPERTIES[videoParam]; + if (videoParams.hasOwnProperty(videoParam)) { + paramValue = videoParams[videoParam]; + } else if (mediaTypesVideoParam !== null && mediaTypesVideoParams.hasOwnProperty(mediaTypesVideoParam)) { + if (mediaTypesVideoParam === 'skip') { + paramValue = mediaTypesVideoParams[mediaTypesVideoParam] === 1 ? SKIPPABLE_ALLOW : SKIPPABLE_NOT_ALLOW; + } else { + paramValue = mediaTypesVideoParams[mediaTypesVideoParam]; } } + + if (typeof paramValue !== 'undefined') { + giBidRequest[videoParam] = Array.isArray(paramValue) ? paramValue.join(',') : paramValue; + } } } diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js new file mode 100644 index 00000000000..df33369d6ad --- /dev/null +++ b/modules/gjirafaBidAdapter.js @@ -0,0 +1,119 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'gjirafa'; +const ENDPOINT_URL = 'https://central.gjirafa.com/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +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.propertyId && 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 propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + let data = {}; + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } + if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } + if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } + + let adUnitId = bidRequest.adUnitCode; + let placementId = bidRequest.params.placementId; + let sizes = generateSizeParam(bidRequest.sizes); + + return { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: bidRequest.params.skipTime + }; + }); + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents, + data: data + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; + }, + /** + * 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) { + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: responses[i].BidId, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType, + meta: { + advertiserDomains: Array.isArray(responses[i].ADomain) ? responses[i].ADomain : [] + } + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +registerBidder(spec); diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index 1ec8222d8de..fb4960d61f6 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -1,36 +1,67 @@ # Overview -Module Name: Gjirafa Bidder Adapter Module -Type: Bidder Adapter -Maintainer: agonq@gjirafa.com +Module Name: Gjirafa Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com # Description Gjirafa Bidder Adapter for Prebid.js. # Test Parameters +```js var adUnits = [ -{ - code: 'test-div', - sizes: [[728, 90]], // leaderboard - bids: [ - { - bidder: 'gjirafa', - params: { - placementId: '71-3' - } - } - ] -},{ - code: 'test-div', - sizes: [[300, 250]], // mobile rectangle - bids: [ - { - bidder: 'gjirafa', - params: { - minCPM: 0.0001, - minCPC: 0.001, - explicit: true - } - } - ] -} -]; \ No newline at end of file + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [728, 90] + ] + } + }, + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846841', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + }, + { + code: 'test-div', + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846836', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + } +]; +``` diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index 969c04bb451..9ef46f62f1d 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -3,6 +3,17 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'glimpse'; +function transformEachBidResponse(glimpseBid) { + const bid = glimpseBid; + bid.meta = { advertiserDomains: [] }; + + if (glimpseBid.adomain) { + bid.meta.advertiserDomains = glimpseBid.adomain; + } + + return bid; +} + export const spec = { code: BIDDER_CODE, url: 'https://api.glimpseprotocol.io/cloud/v1/vault/prebid', @@ -45,10 +56,10 @@ export const spec = { }, interpretResponse: (serverResponse, _) => { - const bids = []; + let bids = []; try { const { body } = serverResponse; - bids.push(...body); + bids = body.map(transformEachBidResponse); } catch (error) {} return bids; diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js new file mode 100644 index 00000000000..617a1a3d721 --- /dev/null +++ b/modules/glomexBidAdapter.js @@ -0,0 +1,89 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' +import find from 'core-js-pure/features/array/find.js' +import { BANNER } from '../src/mediaTypes.js' + +const ENDPOINT = 'https://prebid.mes.glomex.cloud/request-bid' +const BIDDER_CODE = 'glomex' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.integrationId) { + return true + } + return false + }, + + buildRequests: function (validBidRequests, bidderRequest = {}) { + const refererInfo = bidderRequest.refererInfo || {}; + const gdprConsent = bidderRequest.gdprConsent || {}; + + return { + method: 'POST', + url: `${ENDPOINT}`, + data: { + auctionId: bidderRequest.auctionId, + refererInfo: { + isAmp: refererInfo.isAmp, + numIframes: refererInfo.numIframes, + reachedTop: refererInfo.reachedTop, + referer: refererInfo.referer + }, + 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, originalBidRequest) { + const bidResponses = [] + + originalBidRequest.validBidRequests.forEach(function (bidRequest) { + if (!serverResponse.body) { + return + } + + const matchedBid = find(serverResponse.body.bids, function (bid) { + return String(bidRequest.bidId) === String(bid.id) + }) + + if (matchedBid) { + const bidResponse = { + requestId: bidRequest.bidId, + cpm: matchedBid.cpm, + width: matchedBid.width, + height: matchedBid.height, + creativeId: matchedBid.creativeId, + dealId: matchedBid.dealId, + currency: matchedBid.currency, + netRevenue: matchedBid.netRevenue, + ttl: matchedBid.ttl, + ad: matchedBid.ad, + meta: { + advertiserDomains: matchedBid.adomain ? matchedBid.adomain : [] + } + } + + bidResponses.push(bidResponse) + } + }) + return bidResponses + } +}; + +registerBidder(spec) diff --git a/modules/glomexBidAdapter.md b/modules/glomexBidAdapter.md new file mode 100644 index 00000000000..d52ed88ff16 --- /dev/null +++ b/modules/glomexBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Glomex Bidder Adapter +Module Type: Bidder Adapter +Maintainer: integration-squad@services.glomex.com +``` + +# Description + +Module to use the Glomex Player with prebid.js + +# Test Parameters +``` + var adUnits = [ + { + code: "banner", + mediaTypes: { + banner: { + sizes: [[640, 360]] + } + }, + bids: [{ + bidder: "glomex", + params: { + integrationId: '4059a11hkdzuf65i', + playlistId: 'v-bdui4dz7vjq9' + } + }] + } + ]; +``` diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index d9dc8f7641a..2b9bd6db034 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -2,9 +2,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'gmossp'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; +const storage = getStorageManager(); export const spec = { code: BIDDER_CODE, @@ -29,9 +31,10 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const bidRequests = []; - const url = bidderRequest.refererInfo.referer; + const urlInfo = getUrlInfo(bidderRequest.refererInfo); const cur = getCurrencyType(); const dnt = utils.getDNT() ? '1' : '0'; + const imuid = storage.getCookie('_im_uid.1000283') || ''; for (let i = 0; i < validBidRequests.length; i++) { let queryString = ''; @@ -46,7 +49,9 @@ export const spec = { queryString = utils.tryAppendQueryString(queryString, 'bid', bid); queryString = utils.tryAppendQueryString(queryString, 'ver', ver); queryString = utils.tryAppendQueryString(queryString, 'sid', sid); - queryString = utils.tryAppendQueryString(queryString, 'url', url); + queryString = utils.tryAppendQueryString(queryString, 'im_uid', imuid); + queryString = utils.tryAppendQueryString(queryString, 'url', urlInfo.url); + queryString = utils.tryAppendQueryString(queryString, 'ref', urlInfo.ref); queryString = utils.tryAppendQueryString(queryString, 'cur', cur); queryString = utils.tryAppendQueryString(queryString, 'dnt', dnt); @@ -93,6 +98,10 @@ export const spec = { ttl: res.ttl || 300 }; + if (res.adomains) { + utils.deepSetValue(bid, 'meta.advertiserDomains', Array.isArray(res.adomains) ? res.adomains : [res.adomains]); + } + return [bid]; }, @@ -131,4 +140,31 @@ function getCurrencyType() { return 'JPY'; } +function getUrlInfo(refererInfo) { + return { + url: getUrl(refererInfo), + ref: getReferrer(), + }; +} + +function getUrl(refererInfo) { + if (refererInfo && refererInfo.referer) { + return refererInfo.referer; + } + + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } +} + +function getReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} + registerBidder(spec); diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js new file mode 100644 index 00000000000..f45c10195cd --- /dev/null +++ b/modules/gnetBidAdapter.js @@ -0,0 +1,104 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'gnet'; +const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; + +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.websiteId); + }, + + /** + * 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 bidRequests = []; + const referer = bidderRequest.refererInfo.referer; + + utils._each(validBidRequests, (request) => { + const data = {}; + + data.referer = referer; + data.adUnitCode = request.adUnitCode; + data.bidId = request.bidId; + data.transactionId = request.transactionId; + + data.sizes = utils.parseSizesInput(request.sizes); + + data.params = request.params; + + const payloadString = JSON.stringify(data); + + bidRequests.push({ + method: 'POST', + url: ENDPOINT, + mode: 'no-cors', + options: { + withCredentials: false, + }, + data: payloadString + }); + }); + + return bidRequests; + }, + + /** + * 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, requests) { + if (typeof serverResponse !== 'object') { + return []; + } + + const res = serverResponse && serverResponse.body; + + if (utils.isEmpty(res)) { + return []; + } + + if (res.bids) { + const bids = []; + utils._each(res.bids, (bidData) => { + const bid = { + requestId: bidData.bidId, + cpm: bidData.cpm, + currency: bidData.currency, + width: bidData.width, + height: bidData.height, + ad: bidData.ad, + ttl: 300, + meta: { + advertiserDomains: bidData.adomain ? bidData.adomain : [] + }, + creativeId: bidData.creativeId, + netRevenue: true, + }; + bids.push(bid); + }); + + return bids; + } + + return []; + }, +}; + +registerBidder(spec); diff --git a/modules/gnetBidAdapter.md b/modules/gnetBidAdapter.md new file mode 100644 index 00000000000..447d00d8ff2 --- /dev/null +++ b/modules/gnetBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Gnet Bidder Adapter +Module Type: Bidder Adapter +Maintainer: roberto.wu@grumft.com +``` + +# Description + +Connect to Gnet Project exchange for bids. + +# Test Parameters +``` + var adUnits = [ + { + code: '/150790500/4_ZONA_IAB_300x250_5', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'gnet', + params: { + websiteId: '4' + } + } + ] + } + ]; \ No newline at end of file diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js new file mode 100644 index 00000000000..ff6fa5221f3 --- /dev/null +++ b/modules/gothamadsBidAdapter.js @@ -0,0 +1,342 @@ +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 = 'gothamads'; +const ACCOUNTID_MACROS = '[account_id]'; +const URL_ENDPOINT = `https://us-e-node1.gothamads.com/bid?pass=${ACCOUNTID_MACROS}&integration=prebidjs`; +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' + } +}; +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. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + }, + + /** + * 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 && validBidRequests.length === 0) return [] + let accuontId = validBidRequests[0].params.accountId; + const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); + + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + let bids = []; + for (let bidRequest of validBidRequests) { + let impObject = prepareImpObject(bidRequest); + let data = { + id: bidRequest.bidId, + test: config.getConfig('debug') ? 1 : 0, + cur: ['USD'], + device: { + w: winTop.screen.width, + h: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + }, + site: { + page: location.pathname, + host: location.host + }, + source: { + tid: bidRequest.transactionId + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + tmax: bidRequest.timeout, + imp: [impObject], + }; + + 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); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + + bids.push(data) + } + return { + method: 'POST', + url: endpointURL, + data: bids + }; + }, + + /** + * 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 || !serverResponse.body) return [] + let GothamAdsResponse = serverResponse.body; + + let bids = []; + for (let response of GothamAdsResponse) { + 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, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.ttl || 1200, + currency: response.cur || 'USD', + netRevenue: true, + creativeId: response.seatbid[0].bid[0].crid, + dealId: response.seatbid[0].bid[0].dealid, + 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; + bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl; + break; + case NATIVE: + bid.native = parseNative(response.seatbid[0].bid[0].adm); + break; + default: + bid.ad = response.seatbid[0].bid[0].adm; + } + + bids.push(bid); + } + + return bids; + }, +}; + +/** + * Determine type of request + * + * @param bidRequest + * @param type + * @returns {boolean} + */ +const checkRequestType = (bidRequest, type) => { + return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); +} + +const parseNative = admObject => { + const { + assets, + link, + imptrackers, + jstracker + } = admObject.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [jstracker] : 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 + }; + } + }); + + return result; +} + +const prepareImpObject = (bidRequest) => { + let impObject = { + id: bidRequest.transactionId, + secure: 1, + ext: { + placementId: bidRequest.params.placementId + } + }; + if (checkRequestType(bidRequest, BANNER)) { + impObject.banner = addBannerParameters(bidRequest); + } + if (checkRequestType(bidRequest, VIDEO)) { + impObject.video = addVideoParameters(bidRequest); + } + if (checkRequestType(bidRequest, NATIVE)) { + impObject.native = { + ver: NATIVE_VERSION, + request: addNativeParameters(bidRequest) + }; + } + return impObject +}; + +const addNativeParameters = bidRequest => { + let impObject = { + id: bidRequest.transactionId, + ver: NATIVE_VERSION, + }; + + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + 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'); + bannerObject.w = size[0]; + bannerObject.h = size[1]; + return bannerObject; +}; + +const parseSizes = (bid, mediaType) => { + let mediaTypes = bid.mediaTypes; + if (mediaType === 'video') { + let size = []; + if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { + size = [ + mediaTypes.video.w, + mediaTypes.video.h + ]; + } else if (Array.isArray(utils.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]; + } + return size; + } + let sizes = []; + if (Array.isArray(mediaTypes.banner.sizes)) { + sizes = mediaTypes.banner.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes + } else { + utils.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/gothamadsBidAdapter.md b/modules/gothamadsBidAdapter.md new file mode 100644 index 00000000000..3105dff6c6c --- /dev/null +++ b/modules/gothamadsBidAdapter.md @@ -0,0 +1,104 @@ +# Overview + +``` +Module Name: GothamAds SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@gothamads.com +``` + +# Description + +Module that connects to GothamAds SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'native_example', + // sizes: [[1, 1]], + 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: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: { + minduration:0, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + w:1920, + h:1080, + protocols:[ + 2 + ], + linearity:1, + api:[ + 1, + 2 + ] + } }, + bids: [ + { + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js new file mode 100644 index 00000000000..c351a5181a9 --- /dev/null +++ b/modules/gptPreAuction.js @@ -0,0 +1,113 @@ +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'GPT Pre-Auction'; +export let _currentConfig = {}; +let hooksAdded = false; + +export const appendGptSlots = adUnits => { + const { customGptSlotMatching } = _currentConfig; + + if (!utils.isGptPubadsDefined()) { + return; + } + + const adUnitMap = adUnits.reduce((acc, adUnit) => { + acc[adUnit.code] = adUnit; + return acc; + }, {}); + + window.googletag.pubads().getSlots().forEach(slot => { + const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching + ? customGptSlotMatching(slot) + : utils.isAdUnitCodeMatchingSlot(slot)); + + if (matchingAdUnitCode) { + const adUnit = adUnitMap[matchingAdUnitCode]; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + + const context = adUnit.ortb2Imp.ext.data; + context.adserver = context.adserver || {}; + context.adserver.name = 'gam'; + context.adserver.adslot = sanitizeSlotPath(slot.getAdUnitPath()); + } + }); +}; + +const sanitizeSlotPath = (path) => { + const gptConfig = config.getConfig('gptPreAuction') || {}; + + if (gptConfig.mcmEnabled) { + return path.replace(/(^\/\d*),\d*\//, '$1/'); + } + + return path; +} + +export const appendPbAdSlot = adUnit => { + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext.data; + const { customPbAdSlot } = _currentConfig; + + if (customPbAdSlot) { + context.pbadslot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adserver.adslot')); + return; + } + + // use context.pbAdSlot if set + if (context.pbadslot) { + return; + } + // use data attribute 'data-adslotid' if set + try { + const adUnitCodeDiv = document.getElementById(adUnit.code); + if (adUnitCodeDiv.dataset.adslotid) { + context.pbadslot = adUnitCodeDiv.dataset.adslotid; + return; + } + } catch (e) {} + // banner adUnit, use GPT adunit if defined + if (utils.deepAccess(context, 'adserver.adslot')) { + context.pbadslot = context.adserver.adslot; + return; + } + context.pbadslot = adUnit.code; +}; + +export const makeBidRequestsHook = (fn, adUnits, ...args) => { + appendGptSlots(adUnits); + adUnits.forEach(adUnit => { + appendPbAdSlot(adUnit); + }); + return fn.call(this, adUnits, ...args); +}; + +const handleSetGptConfig = moduleConfig => { + _currentConfig = utils.pick(moduleConfig, [ + 'enabled', enabled => enabled !== false, + 'customGptSlotMatching', customGptSlotMatching => + typeof customGptSlotMatching === 'function' && customGptSlotMatching, + 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, + ]); + + if (_currentConfig.enabled) { + if (!hooksAdded) { + getHook('makeBidRequests').before(makeBidRequestsHook); + hooksAdded = true; + } + } else { + utils.logInfo(`${MODULE_NAME}: Turning off module`); + _currentConfig = {}; + getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + hooksAdded = false; + } +}; + +config.getConfig('gptPreAuction', config => handleSetGptConfig(config.gptPreAuction)); +handleSetGptConfig({}); diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index d18effa349b..9411f38ebd8 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -1,17 +1,18 @@ import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.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'; +import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'grid'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; +const USER_ID_KEY = 'tmguid'; +const GVLID = 686; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -let hasSynced = false; - +export const storage = getStorageManager(GVLID, BIDDER_CODE); const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -23,6 +24,9 @@ const LOG_ERROR_MESS = { hasEmptySeatbidArray: 'Response has empty seatbid array', hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; + +let hasSynced = false; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ BANNER, VIDEO ], @@ -40,116 +44,220 @@ export const spec = { * * @param {BidRequest[]} validBidRequests - an array of bids * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. + * @return {ServerRequest[]} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; + if (!validBidRequests.length) { + return null; + } let pageKeywords = null; - let reqId; + let jwpseg = null; + let content = null; + let schain = 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 = {}; - bids.forEach(bid => { - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode, mediaTypes} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; + bidsMap[bidId] = bid; + 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.toString(), + tagid: uid.toString(), + ext: { + divid: adUnitCode.toString() + } + }; + if (ortb2Imp && 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 (!utils.isEmpty(keywords)) { + if (!pageKeywords) { + pageKeywords = keywords; + } + impObj.ext.bidder = { keywords }; + } - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); + if (bidFloor) { + impObj.bidfloor = bidFloor; } - const addedSizes = {}; - sizesId.forEach((sizeId) => { - addedSizes[sizeId] = true; - }); - const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); - const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); - bannerSizesId.concat(videoSizesId).forEach((sizeId) => { - if (!addedSizes[sizeId]) { - addedSizes[sizeId] = true; - sizesId.push(sizeId); + 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 (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; + if (impObj.banner || impObj.video) { + imp.push(impObj); } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); + }); + + const source = { + tid: auctionId && auctionId.toString(), + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' } - const slot = slotsMap[adUnitCode]; + }; - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } + if (schain) { + source.ext.schain = schain; + } - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); - }); + 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 + }; - const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null - }); + if (content) { + request.site.content = content; + } - if (configKeywords.length) { - pageKeywords = (pageKeywords || []).concat(configKeywords); + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; } - if (pageKeywords && pageKeywords.length > 0) { - pageKeywords.forEach(deleteValues); + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; } - const payload = { - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; + } - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; } - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; + const fpdUserId = getUserIdFromFPDStorage(); + + if (fpdUserId) { + user = user || {}; + user.id = fpdUserId.toString(); + } + + if (user) { + request.user = user; + } + + const userKeywords = utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; + const siteKeywords = utils.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 (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; } + request.regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa') === true) { + if (!request.regs) { + request.regs = {}; + } + request.regs.coppa = 1; } return { - method: 'GET', + method: 'POST', url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, + data: JSON.stringify(request), + newFormat: true, + bidsMap }; }, /** @@ -162,7 +270,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; let errorMessage; @@ -173,7 +280,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses); }); } if (errorMessage) utils.logError(errorMessage); @@ -203,14 +310,31 @@ export const spec = { } }; -function isPopulatedArray(arr) { - return !!(utils.isArray(arr) && arr.length > 0); -} +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bid.params.bidFloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: bid.sizes.map(([w, h]) => ({w, h})) + }); -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } } + + return floor; } function _getBidFromResponse(respItem) { @@ -224,68 +348,56 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, bidResponses) { +function _addBidResponse(serverBid, bidRequest, bidResponses) { 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 (!errorMessage && !serverBid.adm && !serverBid.nurl) 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: 'USD', - netRevenue: false, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; + const bid = bidRequest.bidsMap[serverBid.impid]; + if (bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId + currency: 'USD', + netRevenue: true, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + }, + dealId: serverBid.dealid + }; - if (serverBid.content_type === 'video') { + if (serverBid.ext && serverBid.ext.bidder && serverBid.ext.bidder.grid && serverBid.ext.bidder.grid.demandSource) { + bidResponse.adserverTargeting = { 'hb_ds': serverBid.ext.bidder.grid.demandSource }; + bidResponse.meta.demandSource = serverBid.ext.bidder.grid.demandSource; + } + + if (serverBid.content_type === 'video') { + if (serverBid.adm) { 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 - }); - } - } 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]; - } - } + if (serverBid.nurl) { + bidResponse.vastUrl = serverBid.nurl; + } + bidResponse.mediaType = VIDEO; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL }); } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + bidResponses.push(bidResponse); } } if (errorMessage) { @@ -293,6 +405,101 @@ function _addBidResponse(serverBid, bidsMap, bidResponses) { } } +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + if (protocols && protocols.length) { + result.protocols = protocols; + } + + return result; +} + +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]); + + if (format.length) { + result.format = format + } + return result; +} + +function makeNewUserIdInFPDStorage() { + if (config.getConfig('localStorageWriteAllowed')) { + const value = utils.generateUUID().replace(/-/g, ''); + + storage.setDataInLocalStorage(USER_ID_KEY, value); + return value; + } + return null; +} + +function getUserIdFromFPDStorage() { + return storage.getDataFromLocalStorage(USER_ID_KEY) || makeNewUserIdInFPDStorage(); +} + +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; +} + function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ diff --git a/modules/gridBidAdapter.md b/modules/gridBidAdapter.md index 77b9bbf0f36..8eb8dfc19fb 100644 --- a/modules/gridBidAdapter.md +++ b/modules/gridBidAdapter.md @@ -9,6 +9,17 @@ Maintainer: grid-tech@themediagrid.com Module that connects to Grid demand source to fetch bids. Grid bid adapter supports Banner and Video (instream and outstream). +#Bidder Config +You can allow writing in localStorage `pbjs.setBidderConfig` for the bidder `grid` +``` +pbjs.setBidderConfig({ + bidders: ["grid"], + config: { + localStorageWriteAllowed: true + } + }) +``` + # Test Parameters ``` var adUnits = [ @@ -20,7 +31,7 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: '1', - priceType: 'gross' // by default is 'net' + bidFloor: 0.5 } } ] @@ -32,7 +43,6 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: 2, - priceType: 'gross', keywords: { brandsafety: ['disaster'], topic: ['stress', 'fear'] diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ffd6c1b250c..4ab8464b115 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -24,6 +24,8 @@ const LOG_ERROR_MESS = { hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; +const VIDEO_KEYS = ['mimes', 'protocols', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ VIDEO ], @@ -39,11 +41,12 @@ export const spec = { !bid.params.secid || !utils.isStr(bid.params.secid) || !bid.params.pubid || !utils.isStr(bid.params.pubid); + const video = utils.deepAccess(bid, 'mediaTypes.video') || {}; + const { protocols = video.protocols, mimes = video.mimes } = utils.deepAccess(bid, 'params.video') || {}; if (!invalid) { - invalid = !bid.params.video || !bid.params.video.protocols || !bid.params.video.mimes; + invalid = !protocols || !mimes; } if (!invalid) { - const {protocols, mimes} = bid.params.video; invalid = !utils.isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && utils.isStr(it))).length; if (!invalid) { invalid = !utils.isArray(protocols) || !protocols.length || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; @@ -63,7 +66,7 @@ export const spec = { const requests = []; bids.forEach(bid => { - const {params, bidderRequestId, sizes} = bid; + const { params, bidderRequestId, sizes } = bid; const payload = { sizes: utils.parseSizesInput(sizes).join(','), r: bidderRequestId, @@ -91,11 +94,32 @@ export const spec = { } } + const video = utils.deepAccess(bid, 'mediaTypes.video') || {}; + const paramsVideo = Object.assign({}, params.video); + VIDEO_KEYS.forEach((key) => { + if (!(key in paramsVideo) && key in video) { + paramsVideo[key] = video[key]; + } + }); + + if (!paramsVideo.size && video.playerSize && video.playerSize.length === 2) { + paramsVideo.size = video.playerSize.join('x'); + } + + if (!('mind' in paramsVideo) && 'minduration' in video) { + paramsVideo.mind = video.minduration; + } + if (!('maxd' in paramsVideo) && 'maxduration' in video) { + paramsVideo.maxd = video.maxduration; + } + + const paramsToSend = Object.assign({}, params, {video: paramsVideo}); + requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, ''), bid: bid, - data: params // content + data: paramsToSend // content }); }); @@ -134,17 +158,19 @@ export const spec = { } const bidResponse = { requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, creativeId: serverBid.auid || bid.bidderRequestId, currency: 'USD', - netRevenue: false, + netRevenue: true, ttl: TIME_TO_LIVE, dealId: serverBid.dealid, vastXml: serverBid.adm, mediaType: VIDEO, + meta: { + advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + }, adResponse: { content: serverBid.adm } diff --git a/modules/growadvertisingBidAdapter.js b/modules/growadvertisingBidAdapter.js new file mode 100644 index 00000000000..0626b137a1b --- /dev/null +++ b/modules/growadvertisingBidAdapter.js @@ -0,0 +1,163 @@ +'use strict'; + +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {triggerPixel} from '../src/utils.js'; + +const BIDDER_CODE = 'growads'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bid) { + return bid.params && !!bid.params.zoneId; + }, + + buildRequests: function (validBidRequests) { + let zoneId; + let domain; + let requestURI; + let data = {}; + const zoneCounters = {}; + + return validBidRequests.map(bidRequest => { + zoneId = utils.getBidIdParameter('zoneId', bidRequest.params); + domain = utils.getBidIdParameter('domain', bidRequest.params); + + if (!(zoneId in zoneCounters)) { + zoneCounters[zoneId] = 0; + } + + if (typeof domain === 'undefined' || domain.length === 0) { + domain = 'portal.growadvertising.com'; + } + + requestURI = 'https://' + domain + '/adserve/bid'; + data = { + type: 'prebidjs', + zoneId: zoneId, + i: zoneCounters[zoneId] + }; + zoneCounters[zoneId]++; + + return { + method: 'GET', + url: requestURI, + data: data, + bidRequest: bidRequest + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const request = bidRequest.bidRequest; + let bidResponses = []; + let CPM; + let width; + let height; + let response; + let isCorrectSize = false; + let isCorrectCPM = true; + let minCPM; + let maxCPM; + let bid = {}; + + let body = serverResponse.body; + + try { + response = JSON.parse(body); + } catch (ex) { + response = body; + } + + if (response && response.status === 'success' && request) { + CPM = parseFloat(response.cpm); + width = parseInt(response.width); + height = parseInt(response.height); + + minCPM = utils.getBidIdParameter('minCPM', request.params); + maxCPM = utils.getBidIdParameter('maxCPM', request.params); + width = parseInt(response.width); + height = parseInt(response.height); + + // Ensure response CPM is within the given bounds + if (minCPM !== '' && CPM < parseFloat(minCPM)) { + isCorrectCPM = false; + } + if (maxCPM !== '' && CPM > parseFloat(maxCPM)) { + isCorrectCPM = false; + } + + if (isCorrectCPM) { + bid = { + requestId: request.bidId, + bidderCode: request.bidder, + creativeId: response.creativeId, + cpm: CPM, + width: width, + height: height, + currency: response.currency, + netRevenue: true, + ttl: response.ttl, + adUnitCode: request.adUnitCode, + referrer: utils.deepAccess(request, 'refererInfo.referer') + }; + + if (response.hasOwnProperty(NATIVE)) { + bid[NATIVE] = { + title: response[NATIVE].title, + body: response[NATIVE].body, + body2: response[NATIVE].body2, + cta: response[NATIVE].cta, + sponsoredBy: response[NATIVE].sponsoredBy, + clickUrl: response[NATIVE].clickUrl, + impressionTrackers: response[NATIVE].impressionTrackers, + }; + + if (response[NATIVE].image) { + bid[NATIVE].image = { + url: response[NATIVE].image.url, + height: response[NATIVE].image.height, + width: response[NATIVE].image.width + }; + } + + if (response[NATIVE].icon) { + bid[NATIVE].icon = { + url: response[NATIVE].icon.url, + height: response[NATIVE].icon.height, + width: response[NATIVE].icon.width + }; + } + bid.mediaType = NATIVE; + isCorrectSize = true; + } else { + bid.ad = response.ad; + bid.mediaType = BANNER; + // Ensure that response ad matches one of the placement sizes. + utils._each(utils.deepAccess(request, 'mediaTypes.banner.sizes', []), function (size) { + if (width === size[0] && height === size[1]) { + isCorrectSize = true; + } + }); + } + + if (isCorrectSize) { + bidResponses.push(bid); + } + } + } + + return bidResponses; + }, + + onBidWon: function (bid) { + if (bid.vurl) { + triggerPixel(bid.vurl); + } + }, +}; + +registerBidder(spec); diff --git a/modules/growadvertisingBidAdapter.md b/modules/growadvertisingBidAdapter.md new file mode 100644 index 00000000000..f17691e9b9f --- /dev/null +++ b/modules/growadvertisingBidAdapter.md @@ -0,0 +1,70 @@ +--- +layout: bidder +title: GrowAdvertising +description: Prebid GrowAdvertising Bidder Adapter +pbjs: true +biddercode: growads +media_types: banner +--- + +### Bid Params + +| Name | Scope | Description | Example | Type | +|----------|----------|-----------|--------------------|----------| +| `zoneId` | required | ZoneId ID | `'unique-zone-id'` | `string` | +| `domain` | optional | Domain | `'example.org'` | `string` | +| `minCPM` | optional | Minimum CPM | `1.5` | `float` | +| `maxCPM` | optional | Maximum CPM | `10.8` | `float` | + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: "growads", + params: { + zoneId: '6WG9JK8-RvKai86-yL980YC-kQFoqXZ', + domain: 'native-test.growadvertising.com' + } + } + ] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + } + }, + bids: [ + { + bidder: 'growads', + params: { + zoneId: 'YpQobqT-vEybhHx-1qaNMFx-Wj3Kwc2', + domain: 'native-test.growadvertising.com' + } + } + ] + }, + ]; +``` diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 54d845be7ad..3a0862b9aec 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -19,7 +19,7 @@ const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins let invalidRequestIds = {}; let browserParams = {}; -let pageViewId = null +let pageViewId = null; // TODO: potential 0 values for browserParams sent to ad server function _getBrowserParams(topWindowUrl) { @@ -28,12 +28,12 @@ function _getBrowserParams(topWindowUrl) { let topUrl let ggad let ns - function getNetworkSpeed () { + function getNetworkSpeed() { const connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection) const Mbps = connection && (connection.downlink || connection.bandwidth) return Mbps ? Math.round(Mbps * 1024) : null } - function getOgURL () { + function getOgURL() { let ogURL = '' const ogURLSelector = "meta[property='og:url']" const head = document && document.getElementsByTagName('head')[0] @@ -83,14 +83,6 @@ function getWrapperCode(wrapper, data) { return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) } -function _getTradeDeskIDParam(userId) { - const unifiedIdObj = {}; - if (userId.tdid) { - unifiedIdObj.tdid = userId.tdid; - } - return unifiedIdObj; -} - function _getDigiTrustQueryParams(userId) { let digiTrustId = userId.digitrustid && userId.digitrustid.data; // Verify there is an ID and this user has not opted out @@ -130,12 +122,13 @@ function _serializeSupplyChainObj(schainObj) { * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ -function isBidRequestValid (bid) { +function isBidRequestValid(bid) { const { params, adUnitCode } = bid; - const id = params.inScreen || params.inScreenPubID || params.inSlot || params.ICV || params.video || params.inVideo; + const legacyParamID = params.inScreen || params.inScreenPubID || params.inSlot || params.ICV || params.video || params.inVideo; + const id = legacyParamID || params.slot || params.native || params.zone || params.pubID; if (invalidRequestIds[id]) { utils.logWarn(`[GumGum] Please check the implementation for ${id} for the placement ${adUnitCode}`); @@ -143,6 +136,8 @@ function isBidRequestValid (bid) { } switch (true) { + case !!(params.zone): break; + case !!(params.pubId): break; case !!(params.inScreen): break; case !!(params.inScreenPubID): break; case !!(params.inSlot): break; @@ -168,7 +163,7 @@ function isBidRequestValid (bid) { * @param {Object} attributes * @returns {Object} */ -function _getVidParams (attributes) { +function _getVidParams(attributes) { const { minduration: mind, maxduration: maxd, @@ -198,13 +193,87 @@ function _getVidParams (attributes) { }; } +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Number} bidfloor + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor(mediaTypes, staticBidFloor, bid) { + const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; + const bidFloor = { floor: 0, currency: 'USD' }; + + if (typeof bid.getFloor === 'function') { + const { currency, floor } = bid.getFloor({ + mediaType: curMediaType, + size: '*' + }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); + + if (staticBidFloor && floor && currency === 'USD') { + bidFloor.floor = Math.max(staticBidFloor, parseFloat(floor)); + } + } else if (staticBidFloor) { + bidFloor.floor = staticBidFloor + } + + return bidFloor; +} + +/** + * loops through bannerSizes array to get greatest slot dimensions + * @param {number[][]} sizes + * @returns {number[]} + */ +function getGreatestDimensions(sizes) { + let maxw = 0; + let maxh = 0; + let greatestVal = 0; + sizes.forEach(bannerSize => { + let [width, height] = bannerSize; + let greaterSide = width > height ? width : height; + if ((greaterSide > greatestVal) || (greaterSide === greatestVal && width >= maxw && height >= maxh)) { + greatestVal = greaterSide; + maxw = width; + maxh = height; + } + }); + + return [maxw, maxh]; +} + +function getEids(userId) { + const idProperties = [ + 'uid', + 'eid', + 'lipbid' + ]; + + return Object.keys(userId).reduce(function (eids, provider) { + const eid = userId[provider]; + switch (typeof eid) { + case 'string': + eids[provider] = eid; + break; + + case 'object': + const idProp = idProperties.filter(prop => eid.hasOwnProperty(prop)); + idProp.length && (eids[provider] = eid[idProp[0]]); + break; + } + return eids; + }, {}); +} + /** * Make a server request from the list of BidRequests. * * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ -function buildRequests (validBidRequests, bidderRequest) { +function buildRequests(validBidRequests, bidderRequest) { const bids = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; @@ -217,48 +286,69 @@ function buildRequests (validBidRequests, bidderRequest) { params = {}, schain, transactionId, - userId = {} + userId = {}, + ortb2Imp } = bidRequest; - const bannerSizes = mediaTypes.banner && mediaTypes.banner.sizes; + const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); + const eids = getEids(userId); + let sizes = [1, 1]; let data = {}; + let gpid = ''; - if (pageViewId) { - data.pv = pageViewId; - } - if (params.bidfloor) { - data.fp = params.bidfloor; - } - if (params.inScreenPubID) { - data.pubId = params.inScreenPubID; - data.pi = 2; + // ADTS-134 Retrieve ID envelopes + for (const eid in eids) data[eid] = eids[eid]; + + // ADJS-1024 + if (utils.deepAccess(ortb2Imp, 'ext.data.adserver.name')) { + gpid = ortb2Imp.ext.data.adserver.adslot } - if (params.inScreen) { - data.t = params.inScreen; - data.pi = 2; + + if (mediaTypes.banner) { + sizes = mediaTypes.banner.sizes; + } else if (mediaTypes.video) { + sizes = mediaTypes.video.playerSize; + data = _getVidParams(mediaTypes.video); } - if (params.inSlot) { - data.si = parseInt(params.inSlot, 10); - data.pi = 3; + + if (pageViewId) { + data.pv = pageViewId; } - if (params.ICV) { - data.ni = parseInt(params.ICV, 10); - data.pi = 5; + + if (floor) { + data.fp = floor; + data.fpc = currency; } - if (params.videoPubID) { - data = Object.assign(data, _getVidParams(mediaTypes.video)); - data.pubId = params.videoPubID; - data.pi = 7; + + if (params.iriscat && typeof params.iriscat === 'string') { + data.iriscat = params.iriscat; } - if (params.video) { - data = Object.assign(data, _getVidParams(mediaTypes.video)); - data.t = params.video; - data.pi = 7; + + if (params.irisid && typeof params.irisid === 'string') { + data.irisid = params.irisid; } - if (params.inVideo) { - data = Object.assign(data, _getVidParams(mediaTypes.video)); - data.t = params.inVideo; - data.pi = 6; + + if (params.zone || params.pubId) { + params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); + + data.pi = 2; // inscreen + // override pi if the following is found + if (params.slot) { + const [maxw, maxh] = getGreatestDimensions(sizes); + data.maxw = maxw; + data.maxh = maxh; + data.si = parseInt(params.slot, 10); + data.pi = 3; + data.bf = sizes.reduce((acc, curSlotDim) => `${acc}${acc && ','}${curSlotDim[0]}x${curSlotDim[1]}`, ''); + } else if (params.native) { + data.ni = parseInt(params.native, 10); + data.pi = 5; + } else if (mediaTypes.video) { + data.pi = mediaTypes.video.linearity === 2 ? 6 : 7; // invideo : video + } + } else { // legacy params + data = { ...data, ...handleLegacyParams(params, sizes) } } + if (gdprConsent) { data.gdprApplies = gdprConsent.gdprApplies ? 1 : 0; } @@ -278,28 +368,65 @@ function buildRequests (validBidRequests, bidderRequest) { tId: transactionId, pi: data.pi, selector: params.selector, - sizes: bannerSizes, + sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), _getTradeDeskIDParam(userId)) + data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), { gpid }) }) }); return bids; } +function handleLegacyParams(params, sizes) { + const data = {}; + if (params.inScreenPubID) { + data.pubId = params.inScreenPubID; + data.pi = 2; + } + if (params.inScreen) { + data.t = params.inScreen; + data.pi = 2; + } + if (params.inSlot) { + const [maxw, maxh] = getGreatestDimensions(sizes); + data.maxw = maxw; + data.maxh = maxh; + data.si = parseInt(params.inSlot, 10); + data.pi = 3; + data.bf = sizes.reduce((acc, curSlotDim) => `${acc}${acc && ','}${curSlotDim[0]}x${curSlotDim[1]}`, ''); + } + if (params.ICV) { + data.ni = parseInt(params.ICV, 10); + data.pi = 5; + } + if (params.videoPubID) { + data.pubId = params.videoPubID; + data.pi = 7; + } + if (params.video) { + data.t = params.video; + data.pi = 7; + } + if (params.inVideo) { + data.t = params.inVideo; + data.pi = 6; + } + return 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. */ -function interpretResponse (serverResponse, bidRequest) { +function interpretResponse(serverResponse, bidRequest) { const bidResponses = [] const serverResponseBody = serverResponse.body if (!serverResponseBody || serverResponseBody.err) { - const data = bidRequest.data || {} - const id = data.t || data.si || data.ni || data.pubId; + const data = bidRequest.data || {}; + const id = data.si || data.ni || data.t || data.pubId; const delayTime = serverResponseBody ? serverResponseBody.err.drt : DELAY_REQUEST_TIME; invalidRequestIds[id] = { productId: data.pi, timestamp: new Date().getTime() }; @@ -313,10 +440,16 @@ function interpretResponse (serverResponse, bidRequest) { ad: { price: 0, id: 0, - markup: '' + markup: '', + width: 0, + height: 0 }, pag: { pvid: 0 + }, + meta: { + adomain: [], + mediaType: '' } } const { @@ -324,26 +457,42 @@ function interpretResponse (serverResponse, bidRequest) { price: cpm, id: creativeId, markup, - cur + cur, + width: responseWidth, + height: responseHeight, + maxw, + maxh }, cw: wrapper, pag: { pvid }, - jcsi - } = Object.assign(defaultResponse, serverResponseBody) - let data = bidRequest.data || {} - let product = data.pi - let isTestUnit = (product === 3 && data.si === 9) - let sizes = utils.parseSizesInput(bidRequest.sizes) - let [width, height] = sizes[0].split('x') - - // return 1x1 when breakout expected - if ((product === 2 || product === 5) && includes(sizes, '1x1')) { - width = '1' - height = '1' + jcsi, + meta: { + adomain: advertiserDomains, + mediaType: type + } + } = Object.assign(defaultResponse, serverResponseBody); + let data = bidRequest.data || {}; + let product = data.pi; + let mediaType = (product === 6 || product === 7) ? VIDEO : BANNER; + let isTestUnit = (product === 3 && data.si === 9); + let metaData = { + advertiserDomains: advertiserDomains || [], + mediaType: type || mediaType + }; + let sizes = utils.parseSizesInput(bidRequest.sizes); + + if (maxw && maxh) { + sizes = [`${maxw}x${maxh}`]; + } else if (product === 5 && includes(sizes, '1x1')) { + sizes = ['1x1']; + } else if (product === 2 && includes(sizes, '1x1')) { + sizes = responseWidth && responseHeight ? [`${responseWidth}x${responseHeight}`] : utils.parseSizesInput(bidRequest.sizes) } + let [width, height] = sizes[0].split('x'); + if (jcsi) { serverResponseBody.jcsi = JCSI } @@ -355,9 +504,9 @@ function interpretResponse (serverResponse, bidRequest) { bidResponses.push({ // dealId: DEAL_ID, // referrer: REFERER, - ...(product === 7 && { vastXml: markup }), ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, - ...(product === 6 && {ad: markup}), + ...(mediaType === VIDEO && { ad: markup, vastXml: markup }), + mediaType, cpm: isTestUnit ? 0.1 : cpm, creativeId, currency: cur || 'USD', @@ -365,7 +514,8 @@ function interpretResponse (serverResponse, bidRequest) { netRevenue: true, requestId: bidRequest.id, ttl: TIME_TO_LIVE, - width + width, + meta: metaData }) } return bidResponses @@ -378,7 +528,7 @@ function interpretResponse (serverResponse, bidRequest) { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ -function getUserSyncs (syncOptions, serverResponses) { +function getUserSyncs(syncOptions, serverResponses) { const responses = serverResponses.map((response) => { return (response.body && response.body.pxs && response.body.pxs.scr) || [] }) diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index f47666e9628..57d56235d1c 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -8,14 +8,87 @@ Maintainer: engineering@gumgum.com # Description -GumGum adapter for Prebid.js 1.0 +GumGum adapter for Prebid.js +Please note that both video and in-video products require a mediaType of video. +In-screen and slot products should have a mediaType of banner. # Test Parameters ``` +var adUnits = [ + { + code: 'slot-placement', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + slot: '15901', // GumGum Slot ID given to the client, + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'inscreen-placement', + sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'video-placement', + sizes: [[300, 50]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 1, + protocols: [1, 2] + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'ggumtest', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + } +]; +``` + +# Legacy Test Parameters +``` var adUnits = [ { code: 'test-div', sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [ { bidder: 'gumgum', @@ -28,6 +101,11 @@ var adUnits = [ },{ code: 'test-div', sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, bids: [ { bidder: 'gumgum', @@ -40,6 +118,18 @@ var adUnits = [ },{ code: 'test-div', sizes: [[300, 50]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + minduration: 1, + maxduration: 2, + linearity: 2, + startdelay: 1, + placement: 1, + protocols: [1, 2] + } + } bids: [ { bidder: 'gumgum', diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 0d2c22a3f68..7b736780226 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'h12media'; const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; const DEFAULT_CURRENCY = 'USD'; @@ -16,21 +15,31 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { - const requestUrl = validBidRequests[0].params.endpointdom || DEFAULT_URL; - const isiframe = !((window.self === window.top) || window.frameElement); + const isiframe = utils.inIframe(); const screenSize = getClientDimensions(); const docSize = getDocumentDimensions(); - const bidrequests = validBidRequests.map((bidRequest) => { + return validBidRequests.map((bidRequest) => { const bidderParams = bidRequest.params; - const adUnitElement = document.getElementById(bidRequest.adUnitCode); + const requestUrl = bidderParams.endpointdom || DEFAULT_URL; + let pubsubid = bidderParams.pubsubid || ''; + if (pubsubid && pubsubid.length > 32) { + utils.logError('Bidder param \'pubsubid\' should be not more than 32 chars.'); + pubsubid = ''; + } + const pubcontainerid = bidderParams.pubcontainerid; + const adUnitElement = document.getElementById(pubcontainerid || bidRequest.adUnitCode); const ishidden = !isVisible(adUnitElement); - const coords = { + const framePos = getFramePos(); + const coords = isiframe ? { + x: framePos[0], + y: framePos[1], + } : { x: adUnitElement && adUnitElement.getBoundingClientRect().x, y: adUnitElement && adUnitElement.getBoundingClientRect().y, }; - return { + const bidrequest = { bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, adunitId: bidRequest.adUnitCode, @@ -40,33 +49,46 @@ export const spec = { adunitSize: bidRequest.mediaTypes.banner.sizes || [], coords, ishidden, + pubsubid, + pubcontainerid, }; - }); - return { - method: 'POST', - url: requestUrl, - options: {withCredentials: false}, - data: { - gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false, - gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '', - topLevelUrl: window.top.location.href, - refererUrl: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '', - isiframe, - version: '$prebid.version$', - visitorInfo: { - localTime: getLocalDateFormatted(), - dayOfWeek: new Date().getDay(), - screenWidth: screenSize[0], - screenHeight: screenSize[1], - docWidth: docSize[0], - docHeight: docSize[1], - scrollbarx: window.scrollX, - scrollbary: window.scrollY, + let windowTop; + try { + windowTop = window.top; + } catch (e) { + utils.logMessage(e); + windowTop = window; + } + + return { + method: 'POST', + url: requestUrl, + options: {withCredentials: true}, + data: { + gdpr: !!utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies', false), + gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + usp: !!utils.deepAccess(bidderRequest, 'uspConsent', false), + usp_cs: utils.deepAccess(bidderRequest, 'uspConsent', ''), + topLevelUrl: utils.deepAccess(bidderRequest, 'refererInfo.referer', ''), + refererUrl: windowTop.document.referrer, + isiframe, + version: '$prebid.version$', + ExtUserIDs: bidRequest.userId, + visitorInfo: { + localTime: getLocalDateFormatted(), + dayOfWeek: new Date().getDay(), + screenWidth: screenSize[0], + screenHeight: screenSize[1], + docWidth: docSize[0], + docHeight: docSize[1], + scrollbarx: windowTop.scrollX, + scrollbary: windowTop.scrollY, + }, + bidrequest, }, - bidrequests, - }, - }; + }; + }); }, interpretResponse: function(serverResponse, bidRequests) { @@ -74,29 +96,28 @@ export const spec = { try { const serverBody = serverResponse.body; if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const bidRequest = find(bidRequests.data.bidrequests, bid => bid.bidId === bidBody.bidId); - const bidResponse = { - currency: serverBody.currency || DEFAULT_CURRENCY, - netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, - ttl: serverBody.ttl || DEFAULT_TTL, - requestId: bidBody.bidId, - cpm: bidBody.cpm, - width: bidBody.width, - height: bidBody.height, - creativeId: bidBody.creativeId, - ad: bidBody.ad, - meta: bidBody.meta, - mediaType: 'banner', - }; - if (bidRequest) { - bidResponse.pubid = bidRequest.pubid; - bidResponse.placementid = bidRequest.placementid; - bidResponse.size = bidRequest.size; - } - bidResponses.push(bidResponse); - }); + if (serverBody.bid) { + const bidBody = serverBody.bid; + const bidRequest = bidRequests.data.bidrequest; + const bidResponse = { + currency: serverBody.currency || DEFAULT_CURRENCY, + netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, + ttl: serverBody.ttl || DEFAULT_TTL, + requestId: bidBody.bidId, + cpm: bidBody.cpm, + width: bidBody.width, + height: bidBody.height, + creativeId: bidBody.creativeId, + ad: bidBody.ad, + meta: bidBody.meta, + mediaType: 'banner', + }; + if (bidRequest) { + bidResponse.pubid = bidRequest.pubid; + bidResponse.placementid = bidRequest.placementid; + bidResponse.size = bidRequest.size; + } + bidResponses.push(bidResponse); } } return bidResponses; @@ -105,47 +126,50 @@ export const spec = { } }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - const serverBody = serverResponses[0].body; + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, usPrivacy) { const syncs = []; + const uspApplies = !!utils.deepAccess(usPrivacy, 'uspConsent', false); + const uspString = utils.deepAccess(usPrivacy, 'uspConsent', ''); gdprConsent = gdprConsent || { gdprApplies: false, consentString: '', }; - if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const userSyncUrls = bidBody.usersync || []; - const userSyncUrlProcess = url => { - return url - .replace('{gdpr}', gdprConsent.gdprApplies) - .replace('{gdpr_cs}', gdprConsent.consentString); - } + const userSyncUrlProcess = url => { + return url + .replace('{gdpr}', gdprConsent.gdprApplies) + .replace('{gdpr_cs}', gdprConsent.consentString) + .replace('{usp}', uspApplies) + .replace('{usp_cs}', uspString); + } - userSyncUrls.forEach(sync => { - if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { - syncs.push({ - type: 'iframe', - url: userSyncUrlProcess(sync.url), - }); - } - if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { - syncs.push({ - type: 'image', - url: userSyncUrlProcess(sync.url), - }); - } + serverResponses.forEach(serverResponse => { + const userSyncUrls = serverResponse.body.usersync || []; + userSyncUrls.forEach(sync => { + if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { + syncs.push({ + type: 'iframe', + url: userSyncUrlProcess(sync.url), }); - }); - } - } + } + if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { + syncs.push({ + type: 'image', + url: userSyncUrlProcess(sync.url), + }); + } + }) + }); return syncs; }, } function getContext(elem) { - return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + try { + return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + } catch (e) { + return undefined; + } } function isDefined(val) { @@ -206,4 +230,24 @@ function getLocalDateFormatted() { return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())} ${two(d.getHours())}:${two(d.getMinutes())}:${two(d.getSeconds())}`; } +function getFramePos() { + let t = window; + let m = 0; + let frmLeft = 0; + let frmTop = 0; + do { + m = m + 1; + try { + if (m > 1) { + t = t.parent + } + frmLeft = frmLeft + t.frameElement.getBoundingClientRect().left; + frmTop = frmTop + t.frameElement.getBoundingClientRect().top; + } catch (o) { /* keep looping */ + } + } while ((m < 100) && (t.parent !== t.self)) + + return [frmLeft, frmTop]; +} + registerBidder(spec); diff --git a/modules/haloIdSystem.js b/modules/haloIdSystem.js new file mode 100644 index 00000000000..3011569a17d --- /dev/null +++ b/modules/haloIdSystem.js @@ -0,0 +1,96 @@ +/** + * This module adds HaloID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/haloIdSystem + * @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 = 'haloId'; +const AU_GVLID = 561; + +export const storage = getStorageManager(AU_GVLID, 'halo'); + +/** + * 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 haloIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{haloId:Object}} + */ + decode(value) { + let haloId = storage.getDataFromLocalStorage('auHaloId'); + if (utils.isStr(haloId)) { + return {haloId: haloId}; + } + return (value && typeof value['haloId'] === 'string') ? { 'haloId': value['haloId'] } : 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://id.halo.ad.gt/api/v1/pbhid`, + config.params.urlArg); + + const resp = function (callback) { + let haloId = storage.getDataFromLocalStorage('auHaloId'); + if (utils.isStr(haloId)) { + const responseObj = {haloId: haloId}; + 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', haloIdSubmodule); diff --git a/modules/haloIdSystem.md b/modules/haloIdSystem.md new file mode 100644 index 00000000000..f740ae58048 --- /dev/null +++ b/modules/haloIdSystem.md @@ -0,0 +1,35 @@ +## Audigent Halo User ID Submodule + +Audigent Halo ID Module. For assistance setting up your module please contact us at [prebid@audigent.com](prebid@audigent.com). + +### Prebid Params + +Individual params may be set for the Audigent Halo ID Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'haloId', + storage: { + name: 'haloId', + type: 'html5' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the HaloID User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the HaloID module - `"haloId"` | `"haloId"` | +| 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. | `"haloid"` | +| 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 Halo 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 | `{"haloId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | +| params | Optional | Object | Used to store params for the id system | +| params.url | Optional | String | Set an alternate GET url for HaloId with this parameter | +| params.urlArg | Optional | Object | Optional url parameter for params.url | diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js new file mode 100644 index 00000000000..d889310a7c2 --- /dev/null +++ b/modules/haloRtdProvider.js @@ -0,0 +1,254 @@ +/** + * This module adds the Audigent Halo provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time data from Audigent + * @module modules/haloRtdProvider + * @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 {submodule} from '../src/hook.js'; +import {isFn, isStr, isArray, deepEqual, isPlainObject, logError} from '../src/utils.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'halo'; +const AU_GVLID = 561; + +export const HALOID_LOCAL_NAME = 'auHaloId'; +export const RTD_LOCAL_NAME = 'auHaloRtd'; +export const storage = getStorageManager(AU_GVLID, SUBMODULE_NAME); + +/** + * Deep set an object unless value present. + * @param {Object} obj + * @param {String} path + * @param {Object} val + */ +function set(obj, path, val) { + const keys = path.split('.'); + const lastKey = keys.pop(); + const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj); + lastObj[lastKey] = lastObj[lastKey] || val; +} + +/** + * Deep object merging with array deduplication. + * @param {Object} target + * @param {Object} sources + */ +function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isPlainObject(target) && isPlainObject(source)) { + for (const key in source) { + if (isPlainObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else if (isArray(source[key])) { + if (!target[key]) { + Object.assign(target, { [key]: source[key] }); + } else if (isArray(target[key])) { + source[key].forEach(obj => { + let e = 1; + for (let i = 0; i < target[key].length; i++) { + if (deepEqual(target[key][i], obj)) { + e = 0; + break; + } + } + if (e) { + target[key].push(obj); + } + }); + } + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} + +/** + * Lazy merge objects. + * @param {Object} target + * @param {Object} source + */ +function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; + } + + if (!isPlainObject(source)) { + source = {}; + } + + return mergeDeep(target, source); +} + +/** + * Param or default. + * @param {String} param + * @param {String} defaultVal + */ +function paramOrDefault(param, defaultVal, arg) { + if (isFn(param)) { + return param(arg); + } else if (isStr(param)) { + return param; + } + return defaultVal; +} + +/** + * Add real-time data & merge segments. + * @param {Object} bidConfig + * @param {Object} rtd + * @param {Object} rtdConfig + */ +export function addRealTimeData(bidConfig, rtd, rtdConfig) { + if (rtdConfig.params && rtdConfig.params.handleRtd) { + rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); + } else { + if (isPlainObject(rtd.ortb2)) { + let ortb2 = config.getConfig('ortb2') || {}; + config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); + } + + if (isPlainObject(rtd.ortb2b)) { + let bidderConfig = config.getBidderConfig(); + + Object.keys(rtd.ortb2b).forEach(bidder => { + let rtdOptions = rtd.ortb2b[bidder] || {}; + + let bidderOptions = {}; + if (isPlainObject(bidderConfig[bidder])) { + bidderOptions = bidderConfig[bidder]; + } + + config.setBidderConfig({ + bidders: [bidder], + config: mergeLazy(bidderOptions, rtdOptions) + }); + }); + } + } +} + +/** + * Real-time data retrieval from Audigent + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ +export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params) && rtdConfig.params.segmentCache) { + let jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME); + + if (jsonData) { + let data = JSON.parse(jsonData); + + if (data.rtd) { + addRealTimeData(bidConfig, data.rtd, rtdConfig); + onDone(); + return; + } + } + } + + const userIds = (getGlobal()).getUserIds(); + + let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + if (isStr(haloId)) { + (getGlobal()).refreshUserIds({submoduleNames: 'haloId'}); + userIds.haloId = haloId; + getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); + } else { + var script = document.createElement('script'); + script.type = 'text/javascript'; + + window.pubHaloCb = (haloId) => { + userIds.haloId = haloId; + getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); + } + + const haloIdUrl = rtdConfig.params && rtdConfig.params.haloIdUrl; + script.src = paramOrDefault(haloIdUrl, 'https://id.halo.ad.gt/api/v1/haloid', userIds); + document.getElementsByTagName('head')[0].appendChild(script); + } +} + +/** + * Async rtd retrieval from Audigent + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + * @param {Object} userIds + */ +export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) { + let reqParams = {}; + + if (isPlainObject(rtdConfig)) { + set(rtdConfig, 'params.requestParams.ortb2', config.getConfig('ortb2')); + reqParams = rtdConfig.params.requestParams; + } + + if (isPlainObject(window.pubHaloPm)) { + reqParams.pubHaloPm = window.pubHaloPm; + } + + const url = `https://seg.halo.ad.gt/api/v1/rtd`; + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.rtd) { + addRealTimeData(bidConfig, data.rtd, rtdConfig); + onDone(); + storage.setDataInLocalStorage(RTD_LOCAL_NAME, JSON.stringify(data)); + } else { + onDone(); + } + } catch (err) { + logError('unable to parse audigent segment data'); + onDone(); + } + } else if (req.status === 204) { + // unrecognized partner config + onDone(); + } + }, + error: function () { + onDone(); + logError('unable to get audigent segment data'); + } + }, + JSON.stringify({'userIds': userIds, 'config': reqParams}), + {contentType: 'application/json'} + ); +} + +/** + * Module init + * @param {Object} provider + * @param {Objkect} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const haloSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getRealTimeData, + init: init +}; + +submodule(MODULE_NAME, haloSubmodule); diff --git a/modules/haloRtdProvider.md b/modules/haloRtdProvider.md new file mode 100644 index 00000000000..4a264af9e2e --- /dev/null +++ b/modules/haloRtdProvider.md @@ -0,0 +1,126 @@ +## Audigent Halo Real-time Data Submodule + +Audigent is a next-generation, 1st party data management platform and the +world’s first "data agency", powering the programmatic landscape and DTC +eCommerce with actionable 1st party audience and contextual data from the +world’s most influential retailers, lifestyle publishers, content creators, +athletes and artists. + +The Halo real-time data module in Prebid has been built so that publishers +can maximize the power of their first-party audiences and contextual data. +This module provides both an integrated cookieless Halo identity with real-time +contextual and audience segmentation solution that seamlessly and easily +integrates into your existing Prebid deployment. + +Users, devices, content, cohorts and other features are identified and utilized +to augment every bid request with targeted, 1st party data-derived segments +before being submitted to supply-side platforms. Enriching the bid request with +robust 1st party audience and contextual data, Audigent's Halo RTD module +optimizes targeting, increases the number of bids, increases bid value, +and drives additional incremental revenue for publishers. + +### Publisher Usage + +Compile the Halo RTD module into your Prebid build: + +`gulp build --modules=userId,unifiedIdSystem,rtdModule,haloRtdProvider,appnexusBidAdapter` + +Add the Halo 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. Please work with your Audigent Prebid support team +(prebid@audigent.com) on which version of Prebid.js supports different bidder +and segment configurations. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 5000, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Halo Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'halo' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.handleRtd | Function | A passable RTD handler that allows custom adunit and ortb2 logic to be configured. The function signature is (bidConfig, rtd, rtdConfig, pbConfig) => {}. | Optional | +| params.segmentCache | Boolean | This parameter tells the Halo RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | +| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | +| params.haloIdUrl | String | Parameter to specify alternate haloid endpoint url. | Optional | + +### Publisher Customized RTD Handling +As indicated above, it is possible to provide your own bid augmentation +functions rather than simply merging supplied data. This is useful if you +want to perform custom bid augmentation and logic with Halo real-time data +prior to the bid request being sent. Simply add your custom logic to the +optional handleRtd parameter and provide your custom RTD handling logic there. + +Please see the following example, which provides a function to modify bids for +a bid adapter called adBuzz and perform custom logic on bidder parameters. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: auctionDelay, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz' && rtd['adBuzz'][0].value != 'excludeSeg') { + bid.params.adBuzzCustomSegments.push(rtd['adBuzz'][0].id); + } + } + } + }, + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +The handleRtd function can also be used to configure custom ortb2 data +processing. Please see the examples available in the haloRtdProvider_spec.js +tests and work with your Audigent Prebid integration team (prebid@audigent.com) +on how to best configure your own Halo RTD & Open RTB data handlers. + +### Testing + +To view an example of available segments returned by Audigent's backends: + +`gulp serve --modules=userId,unifiedIdSystem,rtdModule,haloRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/haloRtdProvider_example.html` diff --git a/modules/haxmediaBidAdapter.md b/modules/haxmediaBidAdapter.md new file mode 100644 index 00000000000..f661a9e4e71 --- /dev/null +++ b/modules/haxmediaBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: haxmedia Bidder Adapter +Module Type: haxmedia Bidder Adapter +Maintainer: haxmixqk@haxmediapartners.io +``` + +# Description + +Module that connects to haxmedia demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + } + ]; +``` diff --git a/modules/hpmdnetworkBidAdapter.js b/modules/hpmdnetworkBidAdapter.js deleted file mode 100644 index 5cc28ab6362..00000000000 --- a/modules/hpmdnetworkBidAdapter.js +++ /dev/null @@ -1,96 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'hpmdnetwork'; -const BIDDER_CODE_ALIAS = 'hpmd'; -const HPMDNETWORK_HOST = 'https://banner.hpmdnetwork.ru/bidder/request'; -const DEFAULT_TTL = 300; -const DEFAULT_CURRENCY = 'RUB'; - -export const spec = { - code: BIDDER_CODE, - aliases: [ BIDDER_CODE_ALIAS ], - supportedMediaTypes: [ BANNER ], - isBidRequestValid: isBidRequestValid, - buildRequests: buildRequests, - interpretResponse: interpretResponse, -}; - -function isBidRequestValid(bid) { - const { placementId } = bid.params; - return !!placementId; -} - -function buildRequests(validBidRequests, bidderRequest) { - const payload = {}; - payload.places = []; - - validBidRequests.forEach((bidRequest) => { - const place = { - id: bidRequest.bidId, - placementId: bidRequest.params.placementId + '', - }; - payload.places.push(place); - }); - - payload.url = bidderRequest.refererInfo.referer; - payload.settings = { currency: DEFAULT_CURRENCY }; - - return { - method: 'POST', - url: HPMDNETWORK_HOST, - data: payload, - }; -} - -function interpretResponse(serverResponse) { - const { body } = serverResponse; - const bidResponses = []; - - if (body.bids) { - body.bids.forEach((bid) => { - const size = getCreativeSize(bid); - const bidResponse = { - requestId: bid.id, - cpm: bid.cpm, - ad: wrapDisplayUrl(bid.displayUrl), - width: size.width, - height: size.height, - creativeId: bid.creativeId || generateRandomInt(), - currency: bid.currency || DEFAULT_CURRENCY, - netRevenue: true, - ttl: bid.ttl || DEFAULT_TTL, - }; - - bidResponses.push(bidResponse); - }); - } - - return bidResponses; -} - -function wrapDisplayUrl(displayUrl) { - return ``; -} - -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 5671756e0b2..15f8acd824f 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -10,12 +10,14 @@ const DSP_ENDPOINT = 'https://hbe198.hybrid.ai/prebidhb'; const TRAFFIC_TYPE_WEB = 1; const PLACEMENT_TYPE_BANNER = 1; const PLACEMENT_TYPE_VIDEO = 2; +const PLACEMENT_TYPE_IN_IMAGE = 3; const TTL = 60; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const placementTypes = { 'banner': PLACEMENT_TYPE_BANNER, - 'video': PLACEMENT_TYPE_VIDEO + 'video': PLACEMENT_TYPE_VIDEO, + 'inImage': PLACEMENT_TYPE_IN_IMAGE }; function buildBidRequests(validBidRequests) { @@ -26,7 +28,8 @@ function buildBidRequests(validBidRequests) { transactionId: validBidRequest.transactionId, sizes: validBidRequest.sizes, placement: placementTypes[params.placement], - placeId: params.placeId + placeId: params.placeId, + imageUrl: params.imageUrl || '' }; return bidRequest; @@ -75,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) { @@ -94,6 +100,33 @@ function buildBid(bidData) { bid.renderer = createRenderer(bid); } } + } else if (bidData.placement === PLACEMENT_TYPE_IN_IMAGE) { + bid.mediaType = BANNER; + bid.inImageContent = { + content: { + content: bidData.content, + actionUrls: {} + } + }; + let actionUrls = bid.inImageContent.content.actionUrls; + actionUrls.loadUrls = bidData.inImage.loadtrackers || []; + actionUrls.impressionUrls = bidData.inImage.imptrackers || []; + actionUrls.scrollActUrls = bidData.inImage.startvisibilitytrackers || []; + actionUrls.viewUrls = bidData.inImage.viewtrackers || []; + actionUrls.stopAnimationUrls = bidData.inImage.stopanimationtrackers || []; + actionUrls.closeBannerUrls = bidData.inImage.closebannertrackers || []; + + if (bidData.inImage.but) { + let inImageOptions = bid.inImageContent.content.inImageOptions = {}; + inImageOptions.hasButton = true; + inImageOptions.buttonLogoUrl = bidData.inImage.but_logo; + inImageOptions.buttonProductUrl = bidData.inImage.but_prod; + inImageOptions.buttonHead = bidData.inImage.but_head; + inImageOptions.buttonHeadColor = bidData.inImage.but_head_colour; + inImageOptions.dynparams = bidData.inImage.dynparams || {}; + } + + bid.ad = wrapAd(bid, bidData); } else { bid.ad = bidData.content; bid.mediaType = BANNER; @@ -116,6 +149,30 @@ function hasVideoMandatoryParams(mediaTypes) { return isHasVideoContext && isPlayerSize; } +function wrapAd(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -133,6 +190,7 @@ export const spec = { !!bid.params.placement && ( (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'inImage' && !!bid.params.imageUrl) || (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) ) ); diff --git a/modules/hybridBidAdapter.md b/modules/hybridBidAdapter.md index 245f010970a..098d8642415 100644 --- a/modules/hybridBidAdapter.md +++ b/modules/hybridBidAdapter.md @@ -52,3 +52,187 @@ var adUnits = [{ }]; ``` +# Sample In-Image Ad Unit + +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [0, 0] + } + }, + bids: [{ + bidder: "hybrid", + params: { + placement: "inImage", + placeId: "102030405060708090000020", + imageUrl: "https://hybrid.ai/images/image.jpg" + } + }] +}]; +``` + +# Example page with In-Image + +```html + + + + + Prebid.js Banner Example + + + + + +

Prebid.js InImage Banner Test

+
+ + +
+ + +``` + +# Example page with In-Image and GPT + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ + +
+ + +``` 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..a1ff57971ba --- /dev/null +++ b/modules/iasRtdProvider.js @@ -0,0 +1,144 @@ +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'ias'; + +let bidResponses = {}; + +/** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ +export function init(config, userConsent) { + 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, adUnitPath) { + const sizes = utils.getAdUnitSizes(bidRequest); + const id = bidRequest.code; + const ss = stringifySlotSizes(sizes); + const p = bidRequest.code; + 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 getPageLevelKeywords(response) { + let result = {}; + if (response.brandSafety) { + 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 getBidRequestData(reqBidsConfigObj, callback, config) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + let isFinish = false; + + const IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; + const { pubId, adUnitPath } = config.params; + const anId = pubId; + let queries = []; + queries.push(['anId', anId]); + + queries = queries.concat(adUnits.reduce(function (acc, request) { + acc.push(['slot', stringifySlot(request, adUnitPath)]); + 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('&')); + + const ajax = ajaxBuilder(); + + ajax(`${IAS_HOST}?${queryString}`, { + success: function (response, request) { + if (!isFinish) { + if (request.status === 200) { + const iasResponse = JSON.parse(response); + const commonBidResponse = {}; + shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse)); + commonBidResponse.slots = iasResponse.slots; + bidResponses = commonBidResponse; + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const rtd = bid.rtd || {}; + const iasRtd = {}; + iasRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], bidResponses); + bid.rtd = Object.assign({}, rtd, iasRtd); + }); + }); + } + isFinish = true; + } + callback(); + }, + error: function () { + utils.logError('failed to retrieve targeting information'); + callback(); + } + }); +} + +function getTargetingData(adUnits, config, userConsent) { + const targeting = {}; + Object.keys(bidResponses).forEach(key => bidResponses[key] === undefined ? delete bidResponses[key] : {}); + try { + adUnits.forEach(function(adUnit) { + targeting[adUnit] = bidResponses; + }); + } catch (err) { + utils.logError('error', err); + } + return targeting; +} + +/** @type {RtdSubmodule} */ +export const iasSubModule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getBidRequestData, + getTargetingData: getTargetingData +}; + +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 ae3c7e55863..d2d64f52738 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -10,11 +10,19 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { getStorageManager } from '../src/storageManager.js'; +import { uspDataHandler } from '../src/adapterManager.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; -const BASE_NB_COOKIE_NAME = 'id5id.1st'; -const NB_COOKIE_EXP_DAYS = (30 * 24 * 60 * 60 * 1000); // 30 days +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 LOG_PREFIX = 'User ID - ID5 submodule: '; + +// order the legacy cookie names in reverse priority order so the last +// cookie in the array is the most preferred to use +const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; const storage = getStorageManager(GVLID, MODULE_NAME); @@ -36,50 +44,106 @@ export const id5IdSubmodule = { * decode the stored id value for passing to bid requests * @function decode * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config * @returns {(Object|undefined)} */ - decode(value) { - if (value && typeof value.ID5ID === 'string') { - // don't lose our legacy value from cache - return { 'id5id': value.ID5ID }; - } else if (value && typeof value.universal_uid === 'string') { - return { 'id5id': value.universal_uid }; + decode(value, config) { + let universalUid; + let linkType = 0; + + if (value && typeof value.universal_uid === 'string') { + universalUid = value.universal_uid; + linkType = value.link_type || linkType; } else { return undefined; } + + let responseObj = { + id5id: { + uid: universalUid, + ext: { + linkType: linkType + } + } + }; + + const abTestingResult = utils.deepAccess(value, 'ab_testing.result'); + switch (abTestingResult) { + case 'control': + // A/B Testing is enabled and user is in the Control Group + utils.logInfo(LOG_PREFIX + 'A/B Testing - user is in the Control Group: ID5 ID is NOT exposed'); + utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', true); + break; + case 'error': + // A/B Testing is enabled, but configured improperly, so skip A/B testing + utils.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 + utils.logInfo(LOG_PREFIX + 'A/B Testing - user is NOT in the Control Group'); + utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', false); + break; + } + + utils.logInfo(LOG_PREFIX + 'Decoded ID', responseObj); + + return responseObj; }, /** * performs action to obtain id and return a value in the callback's response argument * @function getId - * @param {SubmoduleParams} [configParams] - * @param {ConsentData} [consentData] + * @param {SubmoduleConfig} config + * @param {ConsentData} consentData * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams, consentData, cacheIdObj) { - if (!hasRequiredParams(configParams)) { + getId(config, consentData, cacheIdObj) { + if (!hasRequiredConfig(config)) { return undefined; } + + const url = `https://id5-sync.com/g/v2/${config.params.partner}.json`; const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const url = `https://id5-sync.com/g/v2/${configParams.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}`; + const usp = uspDataHandler.getConsentData(); const referer = getRefererInfo(); - const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : ''; - const pubId = (cacheIdObj && cacheIdObj.ID5ID) ? cacheIdObj.ID5ID : ''; // TODO: remove when 1puid isn't needed + const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { - 'partner': configParams.partner, - '1puid': pubId, // TODO: remove when 1puid isn't needed - 'nbPage': incrementNb(configParams), + 'partner': config.params.partner, + 'gdpr': hasGdpr, + 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', - 'pd': configParams.pd || '', 'rf': referer.referer, - 's': signature, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$' }; + // pass in optional data, but only if populated + if (hasGdpr && typeof consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) { + data.gdpr_consent = consentData.consentString; + } + if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) { + data.us_privacy = usp; + } + if (typeof signature !== 'undefined' && !utils.isEmptyStr(signature)) { + data.s = signature; + } + if (typeof config.params.pd !== 'undefined' && !utils.isEmptyStr(config.params.pd)) { + data.pd = config.params.pd; + } + if (typeof config.params.provider !== 'undefined' && !utils.isEmptyStr(config.params.provider)) { + data.provider = config.params.provider; + } + + 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) { const callbacks = { success: response => { @@ -87,21 +151,34 @@ export const id5IdSubmodule = { if (response) { try { responseObj = JSON.parse(response); - resetNb(configParams); + utils.logInfo(LOG_PREFIX + 'response received from the server', responseObj); + + resetNb(config.params.partner); + + if (responseObj.privacy) { + storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(responseObj.privacy), NB_EXP_DAYS); + } + + // TODO: remove after requiring publishers to use localstorage and + // all publishers have upgraded + if (config.storage.type === LOCAL_STORAGE) { + removeLegacyCookies(config.params.partner); + } } catch (error) { - utils.logError(error); + utils.logError(LOG_PREFIX + error); } } callback(responseObj); }, error: error => { - utils.logError(`id5Id: ID fetch encountered an error`, error); + utils.logError(LOG_PREFIX + 'getId fetch encountered an error', error); callback(); } }; + utils.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 }; }, /** @@ -110,43 +187,133 @@ export const id5IdSubmodule = { * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @function extendId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData * @param {Object} cacheIdObj - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ - extendId(configParams, cacheIdObj) { - incrementNb(configParams); + extendId(config, consentData, cacheIdObj) { + hasRequiredConfig(config); + + const partnerId = (config && config.params && config.params.partner) || 0; + incrementNb(partnerId); + + utils.logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); return cacheIdObj; } }; -function hasRequiredParams(configParams) { - if (!configParams || typeof configParams.partner !== 'number') { - utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); +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'); + return false; + } + + if (!config.storage || !config.storage.type || !config.storage.name) { + utils.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`); + } + // 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`); + } + return true; } -function nbCookieName(configParams) { - return hasRequiredParams(configParams) ? `${BASE_NB_COOKIE_NAME}_${configParams.partner}_nb` : undefined; + +export function expDaysStr(expDays) { + return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); } -function nbCookieExpStr(expDays) { - return (new Date(Date.now() + expDays)).toUTCString(); + +export function nbCacheName(partnerId) { + return `${ID5_STORAGE_NAME}_${partnerId}_nb`; } -function storeNbInCookie(configParams, nb) { - storage.setCookie(nbCookieName(configParams), nb, nbCookieExpStr(NB_COOKIE_EXP_DAYS), 'Lax'); +export function storeNbInCache(partnerId, nb) { + storeInLocalStorage(nbCacheName(partnerId), nb, NB_EXP_DAYS); } -function getNbFromCookie(configParams) { - const cacheNb = storage.getCookie(nbCookieName(configParams)); +export function getNbFromCache(partnerId) { + let cacheNb = getFromLocalStorage(nbCacheName(partnerId)); return (cacheNb) ? parseInt(cacheNb) : 0; } -function incrementNb(configParams) { - const nb = (getNbFromCookie(configParams) + 1); - storeNbInCookie(configParams, nb); +function incrementNb(partnerId) { + const nb = (getNbFromCache(partnerId) + 1); + storeNbInCache(partnerId, nb); return nb; } -function resetNb(configParams) { - storeNbInCookie(configParams, 0); +function resetNb(partnerId) { + storeNbInCache(partnerId, 0); +} + +function getLegacyCookieSignature() { + let legacyStoredValue; + LEGACY_COOKIE_NAMES.forEach(function(cookie) { + if (storage.getCookie(cookie)) { + legacyStoredValue = JSON.parse(storage.getCookie(cookie)) || legacyStoredValue; + } + }); + return (legacyStoredValue && legacyStoredValue.signature) || ''; +} + +/** + * Remove our legacy cookie values. Needed until we move all publishers + * to html5 storage in a future release + * @param {integer} partnerId + */ +function removeLegacyCookies(partnerId) { + utils.logInfo(LOG_PREFIX + 'removing legacy cookies'); + LEGACY_COOKIE_NAMES.forEach(function(cookie) { + storage.setCookie(`${cookie}`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_${partnerId}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_last`, ' ', expDaysStr(-1)); + }); +} + +/** + * This will make sure we check for expiration before accessing local storage + * @param {string} key + */ +export function getFromLocalStorage(key) { + const storedValueExp = storage.getDataFromLocalStorage(`${key}_exp`); + // empty string means no expiration set + if (storedValueExp === '') { + return storage.getDataFromLocalStorage(key); + } else if (storedValueExp) { + if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { + return storage.getDataFromLocalStorage(key); + } + } + // if we got here, then we have an expired item or we didn't set an + // expiration initially somehow, so we need to remove the item from the + // local storage + storage.removeDataFromLocalStorage(key); + return null; +} +/** + * Ensure that we always set an expiration in local storage since + * by default it's not required + * @param {string} key + * @param {any} value + * @param {integer} expDays + */ +export function storeInLocalStorage(key, value, expDays) { + storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); + storage.setDataInLocalStorage(`${key}`, value); +} + +/** + * gets the existing abTesting config or generates a default config with abTesting off + * + * @param {SubmoduleConfig|undefined} config + * @returns {Object} an object which always contains at least the property "enabled" + */ +function getAbTestingConfig(config) { + return utils.deepAccess(config, 'params.abTesting', { enabled: false }); } submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md new file mode 100644 index 00000000000..8ffe29e091f --- /dev/null +++ b/modules/id5IdSystem.md @@ -0,0 +1,68 @@ +# 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). 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. + +## ID5 Universal 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 privacy policy is at [https://www.id5.io/platform-privacy-policy](https://www.id5.io/platform-privacy-policy). + +## ID5 Universal ID Configuration + +First, make sure to add the ID5 submodule to your Prebid.js package with: + +``` +gulp build --modules=id5IdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'id5Id', + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: 'MT1iNTBjY...', // optional, see table below for a link to how to generate this + abTesting: { // optional + enabled: true, // false by default + controlGroupPct: 0.1 // valid values are 0.0 - 1.0 (inclusive) + } + }, + storage: { + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 Universal ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| 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` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | + +**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). + +### A Note on A/B Testing + +Publishers may want to test the value of the ID5 ID with their downstream partners. While there are various ways to do this, A/B testing is a standard approach. Instead of publishers manually enabling or disabling the ID5 User ID Module based on their control group settings (which leads to fewer calls to ID5, reducing our ability to recognize the user), we have baked this in to our module directly. + +To turn on A/B Testing, simply edit the configuration (see above table) to enable it and set what percentage of users you would like to set for the control group. The control group is the set of user where an ID5 ID will not be exposed in to bid adapters or in the various user id functions available on the `pbjs` global. An additional value of `ext.abTestingControlGroup` will be set to `true` or `false` that can be used to inform reporting systems that the user was in the control group or not. It's important to note that the control group is user based, and not request based. In other words, from one page view to another, a user will always be in or out of the control group. diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js new file mode 100644 index 00000000000..2a3a86cf270 --- /dev/null +++ b/modules/idImportLibrary.js @@ -0,0 +1,243 @@ +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; +let conf; +const LOG_PRE_FIX = 'ID-Library: '; +const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; +const CONF_DEFAULT_FULL_BODY_SCAN = false; +const OBSERVER_CONFIG = { + subtree: true, + attributes: true, + attributeOldValue: false, + childList: true, + attirbuteFilter: ['value'], + characterData: true, + characterDataOldValue: false +}; +const logInfo = createLogInfo(LOG_PRE_FIX); +const logError = createLogError(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +function createLogError(prefix) { + return function (...strings) { + utils.logError(prefix + ' ', ...strings); + } +} + +function getEmail(value) { + const matched = value.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi); + if (!matched) { + return null; + } + logInfo('Email found: ' + matched[0]); + return matched[0]; +} + +function bodyAction(mutations, observer) { + 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'); + return; + } + + const body = document.body.innerHTML; + email = getEmail(body); + if (email !== null) { + logInfo(`Email obtained from the body ${email}`); + observer.disconnect(); + logInfo('Post data on email found in body'); + postData(); + } +} + +function targetAction(mutations, observer) { + 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); + observer.disconnect(); + logInfo('Post data on email found in target'); + postData(); + return; + } + } + } +} + +function addInputElementsElementListner(conf) { + 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}`); + inputs[i].addEventListener('change', event => processInputChange(event)); + inputs[i].addEventListener('blur', event => processInputChange(event)); + } +} + +function removeInputElementsElementListner() { + logInfo('Removing input element listeners'); + const inputs = document.querySelectorAll('input[type=text], input[type=email]'); + + for (var i = 0; i < inputs.length; i++) { + inputs[i].removeEventListener('change', event => processInputChange(event)); + inputs[i].removeEventListener('blur', event => processInputChange(event)); + } +} + +function processInputChange(event) { + const value = event.target.value; + logInfo(`Modified Value of input ${event.target.value}`); + email = getEmail(value); + if (email !== null) { + logInfo('Email found in input ' + email); + postData(); + removeInputElementsElementListner(); + } +} + +function debounce(func, wait, immediate) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + if (callNow) { + func.apply(context, args); + } else { + logInfo('Debounce wait time ' + wait); + timeout = setTimeout(later, wait); + } + }; +}; + +function handleTargetElement() { + const targetObserver = new MutationObserver(debounce(targetAction, conf.debounce, false)); + + const targetElement = document.getElementById(conf.target); + if (targetElement) { + email = targetElement.innerText; + + if (!email) { + 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'); + 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'); + postData(); + return; + } + addInputElementsElementListner(); + if (conf.fullscan === true) { + const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false)); + bodyObserver.observe(document.body, OBSERVER_CONFIG); + } +} + +function doesInputElementsHaveEmail() { + const inputs = document.getElementsByTagName('input'); + + for (let index = 0; index < inputs.length; ++index) { + const curInput = inputs[index]; + email = getEmail(curInput.value); + if (email !== null) { + return true; + } + } + return false; +} + +function syncCallback() { + return { + success: function () { + logInfo('Data synced successfully.'); + }, + error: function () { + logInfo('Data sync failed.'); + } + } +} + +function postData() { + (getGlobal()).refreshUserIds(); + const userIds = (getGlobal()).getUserIds(); + if (Object.keys(userIds).length === 0) { + logInfo('No user ids'); + return; + } + logInfo('Users' + userIds); + const syncPayload = {}; + syncPayload.hid = MD5(email).toString(); + syncPayload.uids = userIds; + const payloadString = JSON.stringify(syncPayload); + logInfo(payloadString); + ajax(conf.url, syncCallback(), payloadString, {method: 'POST', withCredentials: true}); +} + +function associateIds() { + if (window.MutationObserver || window.WebKitMutationObserver) { + if (conf.target) { + handleTargetElement(); + } else { + handleBodyElements(); + } + } +} + +export function setConfig(config) { + if (!config) { + logError('Required confirguration not provided'); + return; + } + if (!config.url) { + 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); + } + if (typeof config.fullscan !== 'boolean') { + config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN; + logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); + } + conf = config; + associateIds(); +} + +config.getConfig('idImportLibrary', config => setConfig(config.idImportLibrary)); diff --git a/modules/idImportLibrary.md b/modules/idImportLibrary.md new file mode 100644 index 00000000000..3dd78ee25d8 --- /dev/null +++ b/modules/idImportLibrary.md @@ -0,0 +1,22 @@ +# ID Import Library + +## Configuration Options + +| Parameter | Required | Type | Default | Description | +| :--------- | :------- | :------ | :------ | :---------- | +| `target` | Yes | String | N/A | ID attribute of the element from which the email can be read. | +| `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. | + +## Example + +```javascript +pbjs.setConfig({ + idImportLibrary: { + target: 'username', + url: 'https://example.com', + debounce: 250, + fullscan: false, + }, +}); diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index c516c06d11a..df7b03b4e6e 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -6,8 +6,11 @@ */ import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; + +export const storage = getStorageManager(); /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -16,6 +19,11 @@ export const identityLinkSubmodule = { * @type {string} */ name: 'identityLink', + /** + * used to specify vendor id + * @type {number} + */ + gvlid: 97, /** * decode the stored id value for passing to bid requests * @function @@ -29,43 +37,49 @@ export const identityLinkSubmodule = { * performs action to obtain id and return a value in the callback's response argument * @function * @param {ConsentData} [consentData] - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams, consentData) { + getId(config, consentData) { + const configParams = (config && config.params) || {}; if (!configParams || typeof configParams.pid !== 'string') { - utils.logError('identityLink submodule requires partner id to be defined'); + utils.logError('identityLink: requires partner id to be defined'); return; } const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; + const tcfPolicyV2 = utils.deepAccess(consentData, 'vendorData.tcfPolicyVersion') === 2; // use protocol relative urls for http or https - const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? '&ct=1&cv=' + gdprConsentString : ''}`; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + utils.logInfo('identityLink: Consent string is required to call envelope API.'); + return; + } + const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}`; let resp; - resp = function(callback) { + resp = function (callback) { // Check ats during callback so it has a chance to initialise. // If ats library is available, use it to retrieve envelope. If not use standard third party endpoint if (window.ats) { - utils.logInfo('ATS exists!'); + utils.logInfo('identityLink: ATS exists!'); window.ats.retrieveEnvelope(function (envelope) { if (envelope) { - utils.logInfo('An envelope can be retrieved from ATS!'); + utils.logInfo('identityLink: An envelope can be retrieved from ATS!'); + setEnvelopeSource(true); callback(JSON.parse(envelope).envelope); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }); } else { - getEnvelope(url, callback); + getEnvelope(url, callback, configParams); } }; - return {callback: resp}; + return { callback: resp }; } }; // return envelope from third party endpoint -function getEnvelope(url, callback) { - utils.logInfo('A 3P retrieval is attempted!'); +function getEnvelope(url, callback, configParams) { const callbacks = { success: response => { let responseObj; @@ -79,11 +93,29 @@ function getEnvelope(url, callback) { callback((responseObj && responseObj.envelope) ? responseObj.envelope : ''); }, error: error => { - utils.logInfo(`identityLink: ID fetch encountered an error`, error); + utils.logInfo(`identityLink: identityLink: ID fetch encountered an error`, error); callback(); } }; - ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); + + if (!configParams.notUse3P && !storage.getCookie('_lr_retry_request')) { + setRetryCookie(); + utils.logInfo('identityLink: A 3P retrieval is attempted!'); + setEnvelopeSource(false); + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + } +} + +function setRetryCookie() { + let now = new Date(); + now.setTime(now.getTime() + 3600000); + storage.setCookie('_lr_retry_request', 'true', now.toUTCString()); +} + +function setEnvelopeSource(src) { + let now = new Date(); + now.setTime(now.getTime() + 2592000000); + storage.setCookie('_lr_env_src_ats', src, now.toUTCString()); } submodule('userId', identityLinkSubmodule); diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js new file mode 100644 index 00000000000..00e8a8bc5e5 --- /dev/null +++ b/modules/idxIdSystem.js @@ -0,0 +1,61 @@ +/** + * This module adds IDx to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/idxIdSystem + * @requires module:modules/userId + */ +import * as utils from '../src/utils.js' +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const IDX_MODULE_NAME = 'idx'; +const IDX_COOKIE_NAME = '_idx'; +export const storage = getStorageManager(); + +function readIDxFromCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; +} + +function readIDxFromLocalStorage() { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(IDX_COOKIE_NAME) : null; +} + +/** @type {Submodule} */ +export const idxIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: IDX_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 idxVal = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + return idxVal ? { + 'idx': idxVal + } : 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() { + const idxString = readIDxFromLocalStorage() || readIDxFromCookie(); + if (typeof idxString == 'string' && idxString) { + try { + const idxObj = JSON.parse(idxString); + return idxObj && idxObj.idx ? { id: idxObj.idx } : undefined; + } catch (error) { + utils.logError(error); + } + } + return undefined; + } +}; +submodule('userId', idxIdSubmodule); diff --git a/modules/idxIdSystem.md b/modules/idxIdSystem.md new file mode 100644 index 00000000000..363120899cb --- /dev/null +++ b/modules/idxIdSystem.md @@ -0,0 +1,22 @@ +## IDx User ID Submodule + +For assistance setting up your module please contact us at [prebid@idx.lat](prebid@idx.lat). + +### Prebid Params + +Individual params may be set for the IDx Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'idx', + }] + } +}); +``` +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the IDx integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `"idx"` | `"idx"` | diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js new file mode 100644 index 00000000000..db2c51ccf51 --- /dev/null +++ b/modules/imRtdProvider.js @@ -0,0 +1,197 @@ +/** + * 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' + ); +} + +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 overwriteFunction = getCustomBidderFunction(moduleConfig, bid.bidder); + if (overwriteFunction) { + overwriteFunction(bid, data, utils, config); + } + }) + }); +} + +/** + * 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 new file mode 100644 index 00000000000..5b0b1dff048 --- /dev/null +++ b/modules/impactifyBidAdapter.js @@ -0,0 +1,279 @@ +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 = 360; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; +const GETCONFIG = config.getConfig; + +const getDeviceType = () => { + // OpenRTB Device type + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; + } + return 2; +} + +const createOpenRtbRequest = (validBidRequests, bidderRequest) => { + // Create request and set imp bids inside + let request = { + id: bidderRequest.auctionId, + validBidRequests, + cur: [DEFAULT_CURRENCY], + imp: [], + source: {tid: bidderRequest.auctionId} + }; + + // Force impactify debugging parameter + if (window.localStorage.getItem('_im_db_bidder') != null) { + request.test = Number(window.localStorage.getItem('_im_db_bidder')); + } + + // Set Schain in request + let schain = utils.deepAccess(validBidRequests, '0.schain'); + if (schain) request.source.ext = { schain: schain }; + + // Set eids + let bidUserId = utils.deepAccess(validBidRequests, '0.userId'); + let eids = createEidsArray(bidUserId); + if (eids.length) { + utils.deepSetValue(request, 'user.ext.eids', eids); + } + + // Set device/user/site + if (!request.device) request.device = {}; + if (!request.site) request.site = {}; + request.device = { + w: window.innerWidth, + h: window.innerHeight, + devicetype: getDeviceType(), + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + }; + request.site = {page: bidderRequest.refererInfo.referer}; + + // Handle privacy settings for GDPR/CCPA/COPPA + let gdprApplies = 0; + if (bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; + } + + if (GETCONFIG('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set buyer uid + utils.deepSetValue(request, 'user.buyeruid', utils.generateUUID()); + + // Create imps with bids + validBidRequests.forEach((bid) => { + let imp = { + id: bid.bidId, + bidfloor: bid.params.bidfloor ? bid.params.bidfloor : 0, + ext: { + impactify: { + appId: bid.params.appId, + format: bid.params.format, + style: bid.params.style + }, + }, + video: { + playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], + context: 'outstream', + mimes: ['video/mp4'], + }, + }; + if (bid.params.container) { + imp.ext.impactify.container = bid.params.container; + } + request.imp.push(imp); + }); + + return request; +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: ['video', 'banner'], + aliases: BIDDER_ALIAS, + + /** + * 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.params.appId || typeof bid.params.appId != 'string' || !bid.params.format || typeof bid.params.format != 'string' || !bid.params.style || typeof bid.params.style != 'string') { + return false; + } + if (bid.params.format != 'screen' && bid.params.format != 'display') { + return false; + } + if (bid.params.style != 'inline' && bid.params.style != 'impact' && bid.params.style != 'static') { + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the bidding request + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // Create a clean openRTB request + let request = createOpenRtbRequest(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: ORIGIN + AUCTIONURI, + data: JSON.stringify(request), + }; + }, + + /** + * 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; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + if (!serverBody.seatbid || !serverBody.seatbid.length) { + return []; + } + + serverBody.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + bidResponses = [ + ...bidResponses, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((bid) => ({ + id: bid.id, + requestId: bid.impid, + cpm: bid.price, + currency: serverBody.cur, + netRevenue: true, + ad: bid.adm, + width: bid.w || 0, + height: bid.h || 0, + ttl: 300, + creativeId: bid.crid || 0, + hash: bid.hash, + expiry: bid.expiry, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length ? bid.adomain : [] + } + })), + ]; + } + }); + + 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 (!serverResponses || serverResponses.length === 0) { + return []; + } + + if (!syncOptions.iframeEnabled) { + return []; + } + + 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 += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + if (document.location.search.match(/pbs_debug=true/)) params += `&pbs_debug=true`; + + return [{ + type: 'iframe', + url: ORIGIN + COOKIESYNCURI + params + }]; + }, + + /** + * 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(`${LOGGER_URI}/log/bidder/won`, null, JSON.stringify(bid), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function(data) { + ajax(`${LOGGER_URI}/log/bidder/timeout`, null, JSON.stringify(data[0]), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + } +}; +registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md new file mode 100644 index 00000000000..3de9a8cfb84 --- /dev/null +++ b/modules/impactifyBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Impactify Bidder Adapter +Module Type: Bidder Adapter +Maintainer: thomas.destefano@impactify.io +``` + +# Description + +Module that connects to the Impactify solution. +The impactify bidder need 3 parameters: + - appId : This is your unique publisher identifier + - format : This is the ad format needed, can be : screen or display + - style : This is the ad style needed, can be : inline, impact or static + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot-div-id', // This is your slot div id + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'screen', + style: 'inline' + } + }] + }]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 3c000258ede..e73df68b625 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -3,12 +3,15 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; +import { createEidsArray } from './userId/eids.js'; +import includes from 'core-js-pure/features/array/includes.js'; const BIDDER_CODE = 'improvedigital'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.1.0', + version: '7.4.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -56,6 +59,13 @@ export const spec = { requestParameters.schain = bidRequests[0].schain; + if (bidRequests[0].userId) { + const eids = createEidsArray(bidRequests[0].userId); + if (eids.length) { + utils.deepSetValue(requestParameters, 'user.ext.eids', eids); + } + } + let requestObj = idClient.createRequest( normalizedBids, // requestObject requestParameters @@ -116,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; @@ -149,6 +158,12 @@ export const spec = { bid.height = 1; } + if (bidObject.adomain) { + bid.meta = { + advertiserDomains: bidObject.adomain + }; + } + bids.push(bid); }); return bids; @@ -194,6 +209,36 @@ function isOutstreamVideo(bid) { return videoMediaType && context === 'outstream'; } +function getVideoTargetingParams(bid) { + const result = {}; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.mediaTypes.video[ key ]; + }); + Object.keys(Object(bid.params.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.params.video[ key ]; + }); + return result; +} + +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return null; + } + const floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + function outstreamRender(bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ @@ -240,13 +285,14 @@ function getNormalizedBidRequest(bid) { const bidId = utils.getBidIdParameter('bidId', bid); const transactionId = utils.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)) { normalizedBidRequest.adTypes = [ VIDEO ]; } + if (isInstreamVideo(bid) || isOutstreamVideo(bid)) { + normalizedBidRequest.video = getVideoTargetingParams(bid); + } if (placementId) { normalizedBidRequest.placementId = placementId; } else { @@ -282,6 +328,13 @@ function getNormalizedBidRequest(bid) { if (currency) { normalizedBidRequest.currency = currency; } + // Floor + let bidFloor = getBidFloor(bid); + let bidFloorCur = null; + if (!bidFloor) { + bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + bidFloorCur = utils.getBidIdParameter('bidFloorCur', bid.params); + } if (bidFloor) { normalizedBidRequest.bidFloor = bidFloor; normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD'; @@ -392,7 +445,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { AD_SERVER_BASE_URL: 'ice.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.3.0', + CLIENT_VERSION: 'JS-6.4.0', MAX_URL_LENGTH: 2083, ERROR_CODES: { MISSING_PLACEMENT_PARAMS: 2, @@ -552,6 +605,9 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (requestParameters.schain) { impressionBidRequestObject.schain = requestParameters.schain; } + if (requestParameters.user) { + impressionBidRequestObject.user = requestParameters.user; + } if (extraRequestParameters) { for (let prop in extraRequestParameters) { impressionBidRequestObject[prop] = extraRequestParameters[prop]; @@ -598,6 +654,21 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.transactionId) { impressionObject.tid = placementObject.transactionId; } + if (!utils.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`); + delete video.skip; + } + } + if (!utils.isEmpty(video)) { + impressionObject.video = video; + } + } if (placementObject.keyValues) { for (let key in placementObject.keyValues) { for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js new file mode 100644 index 00000000000..da03c63fc8a --- /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 * as utils 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(utils.timestamp()).toUTCString()); +} + +export function removeImDataFromLocalStorage() { + storage.removeDataFromLocalStorage(storageKey); + storage.removeDataFromLocalStorage(`${storageKey}_mt`); +} + +function setImDataInCookie(value) { + storage.setCookie( + cookieKey, + value, + new Date(utils.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) { + utils.logError('User ID - imuid submodule: ' + error); + } + } + if (callback && responseObj.uid) { + callback(responseObj.uid); + } + }, + error: error => { + utils.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') { + utils.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 new file mode 100755 index 00000000000..e1edd935587 --- /dev/null +++ b/modules/inmarBidAdapter.js @@ -0,0 +1,110 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'inmar'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['inm'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.partnerId); + }, + + /** + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ + buildRequests: function(validBidRequests, bidderRequest) { + var payload = { + bidderCode: bidderRequest.bidderCode, + auctionId: bidderRequest.auctionId, + bidderRequestId: bidderRequest.bidderRequestId, + bidRequests: validBidRequests, + auctionStart: bidderRequest.auctionStart, + timeout: bidderRequest.timeout, + refererInfo: bidderRequest.refererInfo, + start: bidderRequest.start, + gdprConsent: bidderRequest.gdprConsent, + uspConsent: bidderRequest.uspConsent, + currencyCode: config.getConfig('currency.adServerCurrency'), + coppa: config.getConfig('coppa'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), + prebidVersion: '$prebid.version$' + }; + + var payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: 'https://prebid.owneriq.net:8443/bidder/pb/bid', + data: payloadString, + }; + }, + + /** + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + var response = serverResponse.body; + + try { + if (response) { + var bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.ad, + ttl: response.ttl, + creativeId: response.creativeId, + netRevenue: response.netRevenue, + vastUrl: response.vastUrl, + dealId: response.dealId, + meta: response.meta + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing inmar response', error); + } + return bidResponses; + }, + + /** + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @returns {Array} + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://px.owneriq.net/eucm/p/pb' + }); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/inmarBidAdapter.md b/modules/inmarBidAdapter.md new file mode 100644 index 00000000000..8ed6b998602 --- /dev/null +++ b/modules/inmarBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: Inmar Bidder Adapter +Module Type: Bidder Adapter +Maintainer: oiq_rtb@inmar.com +``` + +# Description + +Connects to Inmar for bids. This adapter supports Display and Video. + +The Inmar adapter requires setup and approval from the Inmar team. +Please reach out to your account manager for more information. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div1', + sizes: [[300, 250],[300, 600]], + bids: [{ + bidder: 'inmar', + params: { + partnerId: 12345, + position: 1 + } + }] + }, + { + code: 'test-div2', + sizes: [[728, 90],[970, 250]], + bids: [{ + bidder: 'inmar', + params: { + partnerId: 12345, + position: 0 + } + }] + } + ]; +``` diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js index aae79818daf..ab7ee07db4a 100644 --- a/modules/innityBidAdapter.js +++ b/modules/innityBidAdapter.js @@ -10,10 +10,6 @@ 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 arrSize = parseSized[0].split('x'); @@ -27,7 +23,7 @@ export const spec = { 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 2a55b5280db..5173f1dca63 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -4,9 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; const CONFIG = { - 'inskin': { - 'BASE_URI': 'https://mfad.inskinad.com/api/v2' - } + BASE_URI: 'https://mfad.inskinad.com/api/v2' }; export const spec = { @@ -97,8 +95,7 @@ export const spec = { } validBidRequests.map(bid => { - let config = CONFIG[bid.bidder]; - ENDPOINT_URL = config.BASE_URI; + ENDPOINT_URL = CONFIG.BASE_URI; const placement = Object.assign({ divName: bid.bidId, @@ -108,8 +105,12 @@ export const spec = { placement.adTypes.push(5, 9, 163, 2163, 3006); + placement.properties = placement.properties || {}; + + placement.properties.screenWidth = screen.width; + placement.properties.screenHeight = screen.height; + if (restrictions.length) { - placement.properties = placement.properties || {}; placement.properties.restrictions = restrictions; } @@ -168,6 +169,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); @@ -188,7 +190,7 @@ export const spec = { const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); window[id] = { - plr_AdSlot: e.source.frameElement, + plr_AdSlot: e.source && e.source.frameElement, bidId: e.data.bidId, bidPrice: bidsMap[e.data.bidId].price, serverResponse diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js new file mode 100644 index 00000000000..a081873e78c --- /dev/null +++ b/modules/insticatorBidAdapter.js @@ -0,0 +1,280 @@ +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { + deepAccess, + generateUUID, + logError, +} from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'insticator'; +const ENDPOINT = 'https://ex.ingage.tech/v1/openrtb'; // production endpoint +const USER_ID_KEY = 'hb_insticator_uid'; +const USER_ID_COOKIE_EXP = 2592000000; // 30 days +const BID_TTL = 300; // 5 minutes +const GVLID = 910; + +export const storage = getStorageManager(GVLID, BIDDER_CODE); + +config.setDefaults({ + insticator: { + endpointUrl: ENDPOINT, + bidTTL: BID_TTL, + }, +}); + +function getUserId() { + let uid; + + if (storage.localStorageIsEnabled()) { + uid = localStorage.getItem(USER_ID_KEY); + } else { + uid = storage.getCookie(USER_ID_KEY); + } + + if (uid && uid.length !== 36) { + uid = undefined; + } + + return uid; +} + +function setUserId(userId) { + if (storage.localStorageIsEnabled()) { + localStorage.setItem(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 buildImpression(bidRequest) { + const format = []; + const sizes = + deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; + + for (const size of sizes) { + format.push({ + w: size[0], + h: size[1], + }); + } + + return { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + banner: { + format, + }, + ext: { + insticator: { + adUnitId: bidRequest.params.adUnitId, + }, + }, + }; +} + +function buildDevice() { + const device = { + w: window.innerWidth, + h: window.innerHeight, + js: true, + ext: { + localStorage: storage.localStorageIsEnabled(), + cookies: storage.cookiesAreEnabled(), + }, + }; + + const deviceConfig = config.getConfig('device'); + + if (typeof deviceConfig === 'object') { + Object.assign(device, deviceConfig); + } + + return device; +} + +function buildRegs(bidderRequest) { + if (bidderRequest.gdprConsent) { + return { + ext: { + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + gdprConsentString: bidderRequest.gdprConsent.consentString, + }, + }; + } + + return {}; +} + +function buildUser() { + const userId = getUserId() || generateUUID(); + + setUserId(userId); + + return { + id: userId, + }; +} + +function buildRequest(validBidRequests, bidderRequest) { + const req = { + id: bidderRequest.bidderRequestId, + tmax: bidderRequest.timeout, + source: { + fd: 1, + tid: bidderRequest.auctionId, + }, + site: { + domain: location.hostname, + page: location.href, + ref: bidderRequest.refererInfo.referer, + }, + device: buildDevice(), + regs: buildRegs(bidderRequest), + user: buildUser(), + imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), + }; + + const params = config.getConfig('insticator.params'); + + if (params) { + req.ext = { + insticator: params, + }; + } + + return req; +} + +function buildBid(bid, bidderRequest) { + const originalBid = bidderRequest.bids.find((b) => b.bidId === bid.impid); + + return { + requestId: bid.impid, + creativeId: bid.crid, + cpm: bid.price, + currency: 'USD', + netRevenue: true, + ttl: bid.exp || config.getConfig('insticator.bidTTL') || BID_TTL, + width: bid.w, + height: bid.h, + mediaType: 'banner', + ad: bid.adm, + adUnitCode: originalBid.adUnitCode, + meta: { + advertiserDomains: bid.bidADomain && bid.bidADomain.length ? bid.bidADomain : [] + }, + }; +} + +function buildBidSet(seatbid, bidderRequest) { + return seatbid.bid.map((bid) => buildBid(bid, bidderRequest)); +} + +function validateSize(size) { + return ( + size instanceof Array && + size.length === 2 && + typeof size[0] === 'number' && + typeof size[1] === 'number' + ); +} + +function validateSizes(sizes) { + return ( + sizes instanceof Array && + sizes.length > 0 && + sizes.map(validateSize).reduce((a, b) => a && b, true) + ); +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + if (!bid.params.adUnitId) { + logError('insticator: missing adUnitId bid parameter'); + return false; + } + + if (!(BANNER in bid.mediaTypes)) { + logError('insticator: expected banner in mediaTypes'); + return false; + } + + if ( + !validateSizes(bid.sizes) && + !validateSizes(bid.mediaTypes.banner.sizes) + ) { + logError('insticator: banner sizes not specified or invalid'); + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const requests = []; + let endpointUrl = config.getConfig('insticator.endpointUrl') || ENDPOINT; + endpointUrl = endpointUrl.replace(/^http:/, 'https:'); + + if (validBidRequests.length > 0) { + requests.push({ + method: 'POST', + url: endpointUrl, + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: JSON.stringify(buildRequest(validBidRequests, bidderRequest)), + bidderRequest, + }); + } + + return requests; + }, + + interpretResponse: function (serverResponse, request) { + const bidderRequest = request.bidderRequest; + const body = serverResponse.body; + + if (!body || body.id !== bidderRequest.bidderRequestId) { + logError('insticator: response id does not match bidderRequestId'); + return []; + } + + if (!body.seatbid) { + return []; + } + + const bidsets = body.seatbid.map((seatbid) => + buildBidSet(seatbid, bidderRequest) + ); + + return bidsets.reduce((a, b) => a.concat(b), []); + }, + + getUserSyncs: function (options, responses) { + const syncs = []; + + for (const response of responses) { + if ( + response.body && + response.body.ext && + response.body.ext.sync instanceof Array + ) { + syncs.push(...response.body.ext.sync); + } + } + + return syncs; + }, +}; + +registerBidder(spec); diff --git a/modules/insticatorBidAdapter.md b/modules/insticatorBidAdapter.md new file mode 100644 index 00000000000..62ee5af4a7d --- /dev/null +++ b/modules/insticatorBidAdapter.md @@ -0,0 +1,53 @@ +Overview +======== + +``` +Module Name: Insticator Adapter +Module Type: Bidder Adapter +Maintainer: contact@insticator.com +``` + +Description +=========== + +This module connects publishers to Insticator exchange of demand sources through Prebid.js. + +### Supported Media Types + +| Type | Support +| --- | --- +| Banner | Fully supported for all approved sizes. + +# Bid Parameters + +Each of the Insticator-specific parameters provided under the `adUnits[].bids[].params` +object are detailed here. + +### Banner + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| adUnitId | Required | String | The ad unit ID provided by Insticator. + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [ + { + bidder: 'insticator', + params: { + adUnitId: 'test' + } + } + ] + } + ] +``` diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js new file mode 100644 index 00000000000..68bb4be79de --- /dev/null +++ b/modules/instreamTracking.js @@ -0,0 +1,114 @@ +import { config } from '../src/config.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { INSTREAM } from '../src/video.js'; +import * as events from '../src/events.js'; +import * as utils from '../src/utils.js'; +import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.json'; + +const {CACHE_ID, UUID} = TARGETING_KEYS; +const {BID_WON, AUCTION_END} = EVENTS; +const {RENDERED} = BID_STATUS; + +const INSTREAM_TRACKING_DEFAULT_CONFIG = { + enabled: false, + maxWindow: 1000 * 60, // the time in ms after which polling for instream delivery stops + pollingFreq: 500 // the frequency of polling +}; + +// Set instreamTracking default values +config.setDefaults({ + 'instreamTracking': utils.deepClone(INSTREAM_TRACKING_DEFAULT_CONFIG) +}); + +const whitelistedResources = /video|fetch|xmlhttprequest|other/; + +/** + * Here the idea is + * find all network entries via performance.getEntriesByType() + * filter it by video cache key in the url + * and exclude the ad server urls so that we dont match twice + * eg: + * dfp ads call: https://securepubads.g.doubleclick.net/gampad/ads?...hb_cache_id%3D55e85cd3-6ea4-4469-b890-84241816b131%26... + * prebid cache url: https://prebid.adnxs.com/pbc/v1/cache?uuid=55e85cd3-6ea4-4469-b890-84241816b131 + * + * if the entry exists, emit the BID_WON + * + * Note: this is a workaround till a better approach is engineered. + * + * @param {Array} adUnits + * @param {Array} bidsReceived + * @param {Array} bidderRequests + * + * @return {boolean} returns TRUE if tracking started + */ +export function trackInstreamDeliveredImpressions({adUnits, bidsReceived, bidderRequests}) { + const instreamTrackingConfig = config.getConfig('instreamTracking') || {}; + // check if instreamTracking is enabled and performance api is available + if (!instreamTrackingConfig.enabled || !window.performance || !window.performance.getEntriesByType) { + return false; + } + + // filter for video bids + const instreamBids = bidsReceived.filter(bid => { + const bidderRequest = utils.getBidRequest(bid.requestId, bidderRequests); + return bidderRequest && utils.deepAccess(bidderRequest, 'mediaTypes.video.context') === INSTREAM && bid.videoCacheKey; + }); + if (!instreamBids.length) { + return false; + } + + // find unique instream ad units + const instreamAdUnitMap = {}; + adUnits.forEach(adUnit => { + if (!instreamAdUnitMap[adUnit.code] && utils.deepAccess(adUnit, 'mediaTypes.video.context') === INSTREAM) { + instreamAdUnitMap[adUnit.code] = true; + } + }); + const instreamAdUnitsCount = Object.keys(instreamAdUnitMap).length; + + const start = Date.now(); + const {maxWindow, pollingFreq, urlPattern} = instreamTrackingConfig; + + let instreamWinningBidsCount = 0; + let lastRead = 0; // offset for performance.getEntriesByType + + function poll() { + // get network entries using the last read offset + const entries = window.performance.getEntriesByType('resource').splice(lastRead); + for (const resource of entries) { + const url = resource.name; + // check if the resource is of whitelisted resource to avoid checking img or css or script urls + if (!whitelistedResources.test(resource.initiatorType)) { + continue; + } + + instreamBids.forEach((bid) => { + // match the video cache key excluding ad server call + const matches = !(url.indexOf(CACHE_ID) !== -1 || url.indexOf(UUID) !== -1) && url.indexOf(bid.videoCacheKey) !== -1; + if (urlPattern && urlPattern instanceof RegExp && !urlPattern.test(url)) { + return; + } + if (matches && bid.status !== RENDERED) { + // video found + instreamWinningBidsCount++; + auctionManager.addWinningBid(bid); + events.emit(BID_WON, bid); + } + }); + } + // update offset + lastRead += entries.length; + + const timeElapsed = Date.now() - start; + if (timeElapsed < maxWindow && instreamWinningBidsCount < instreamAdUnitsCount) { + setTimeout(poll, pollingFreq); + } + } + + // start polling for network entries + setTimeout(poll, pollingFreq); + + return true; +} + +events.on(AUCTION_END, trackInstreamDeliveredImpressions) diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js new file mode 100644 index 00000000000..e79878e0b3a --- /dev/null +++ b/modules/integr8BidAdapter.js @@ -0,0 +1,141 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'integr8'; +const ENDPOINT_URL = 'https://integr8.central.gjirafa.tech/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +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 && bid.params.propertyId && 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 propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + let data = {}; + + if (bidderRequest) { + bidderRequestId = bidderRequest.bidderRequestId; + + if (bidderRequest.refererInfo) { + url = bidderRequest.refererInfo.referer; + } + } + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId) { storageId = bidRequest.params.storageId || ''; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (!Object.keys(data).length && bidRequest.params.data && Object.keys(bidRequest.params.data).length) { data = bidRequest.params.data; } + + return { + sizes: generateSizeParam(bidRequest.sizes), + adUnitId: bidRequest.adUnitCode, + placementId: bidRequest.params.placementId, + bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: utils.deepAccess(bidRequest, 'mediaTypes.video.skipafter', bidRequest.params.skipTime), + floor: getBidFloor(bidRequest) + }; + }); + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents, + data: data + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; + }, + /** + * 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) { + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: responses[i].BidId, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType, + meta: { + advertiserDomains: Array.isArray(responses[i].ADomain) ? responses[i].ADomain : [] + } + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +export function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } + + return null; +} + +registerBidder(spec); diff --git a/modules/integr8BidAdapter.md b/modules/integr8BidAdapter.md new file mode 100644 index 00000000000..eadab7acdb3 --- /dev/null +++ b/modules/integr8BidAdapter.md @@ -0,0 +1,67 @@ +# Overview +Module Name: Integr8 Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com + +# Description +Integr8 Bidder Adapter for Prebid.js. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'integr8', + params: { + propertyId: '105109', //Required + placementId: '846835', //Required + data: { //Optional + catalogs: [{ + catalogId: "699229", + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + }, + { + code: 'test-div', + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'integr8', + params: { + propertyId: '105109', //Required + placementId: '846830', //Required + data: { //Optional + catalogs: [{ + catalogId: "699229", + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + } +]; +``` diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 7d497ea9b1a..e12a765c086 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -1,68 +1,168 @@ -/** - * This module adds IntentIqId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/intentIqIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js' - -const MODULE_NAME = 'intentIqId'; - -/** @type {Submodule} */ -export const intentIqIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * decode the stored id value for passing to bid requests - * @function - * @param {{ctrid:string}} value - * @returns {{intentIqId:string}} - */ - decode(value) { - return (value && typeof value['ctrid'] === 'string') ? { 'intentIqId': value['ctrid'] } : undefined; - }, - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleParams} [configParams] - * @returns {IdResponse|undefined} - */ - getId(configParams) { - if (!configParams || typeof configParams.partner !== 'number') { - utils.logError('User ID - intentIqId submodule requires a valid partner to be defined'); - return; - } - - // use protocol relative urls for http or https - const url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; - const resp = function (callback) { - 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', withCredentials: true}); - }; - return {callback: resp}; - } -}; - -submodule('userId', intentIqIdSubmodule); +/** + * This module adds IntentIqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/intentIqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; + +const PCID_EXPIRY = 365; + +const MODULE_NAME = 'intentIqId'; +export const FIRST_PARTY_KEY = '_iiq_fdata'; + +export const storage = getStorageManager(undefined, MODULE_NAME); + +const INVALID_ID = 'INVALID_ID'; + +/** + * Generate standard UUID string + * @return {string} + */ +function generateGUID() { + let d = new Date().getTime(); + const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + return guid; +} + +/** + * Read Intent IQ data from cookie or local storage + * @param key + * @return {string} + */ +export function readData(key) { + try { + if (storage.hasLocalStorage()) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } catch (error) { + utils.logError(error); + } +} + +/** + * Store Intent IQ data in either cookie or local storage + * expiration date: 365 days + * @param key + * @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore + */ +function storeData(key, value) { + try { + utils.logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); + + if (value) { + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(key, value); + } + const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); + if (storage.cookiesAreEnabled()) { + storage.setCookie(key, value, expiresStr, 'LAX'); + } + } + } catch (error) { + utils.logError(error); + } +} + +/** + * Parse json if possible, else return null + * @param data + * @param {object|null} + */ +function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + utils.logError(err); + return null; + } +} + +/** @type {Submodule} */ +export const intentIqIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{string}} value + * @returns {{intentIqId: {string}}|undefined} + */ + decode(value) { + return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : 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) { + const configParams = (config && config.params) || {}; + if (!configParams || typeof configParams.partner !== 'number') { + utils.logError('User ID - intentIqId submodule requires a valid partner to be defined'); + return; + } + + // Read Intent IQ 1st party id or generate it if none exists + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); + if (!firstPartyData || !firstPartyData.pcid) { + const firstPartyId = generateGUID(); + firstPartyData = { 'pcid': firstPartyId }; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + } + + // use protocol relative urls for http or https + let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : ''; + url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; + url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; + url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; + + const resp = function (callback) { + const callbacks = { + success: response => { + let respJson = tryParse(response); + // If response is a valid json and should save is true + if (respJson && respJson.ls) { + // Store pid field if found in response json + if ('pid' in respJson) { + firstPartyData.pid = respJson.pid; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + } + + // If should save and data is empty, means we should save as INVALID_ID + if (respJson.data == '') { + respJson.data = INVALID_ID; + } + callback(respJson.data); + } else { + callback(); + } + }, + error: error => { + utils.logError(MODULE_NAME + ': ID fetch encountered an error', error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + } +}; + +submodule('userId', intentIqIdSubmodule); diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js new file mode 100644 index 00000000000..958c671e4b9 --- /dev/null +++ b/modules/interactiveOffersBidAdapter.js @@ -0,0 +1,178 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'interactiveOffers'; +const ENDPOINT = 'https://prebid.ioadx.com/bidRequest/?partnerId='; + +const DEFAULT = { + 'OpenRTBBidRequest': {}, + 'OpenRTBBidRequestSite': {}, + 'OpenRTBBidRequestSitePublisher': {}, + 'OpenRTBBidRequestSiteContent': { + language: navigator.language, + }, + 'OpenRTBBidRequestSource': {}, + 'OpenRTBBidRequestDevice': { + ua: navigator.userAgent, + language: navigator.language + }, + 'OpenRTBBidRequestUser': {}, + 'OpenRTBBidRequestImp': {}, + 'OpenRTBBidRequestImpBanner': {}, + 'PrebidBid': { + currency: 'USD', + ttl: 60, + netRevenue: false + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + let ret = true; + if (bid && bid.params) { + if (!bid.params.partnerId) { + utils.logWarn('partnerId must be a valid ID'); + ret = false; + } + if (bid.params.tmax && !utils.isNumber(bid.params.tmax)) { + utils.logWarn('tmax must be a valid numeric ID'); + ret = false; + } + } else { + utils.logWarn('invalid request'); + ret = false; + } + return ret; + }, + buildRequests: function(validBidRequests, bidderRequest) { + let aux = parseRequestPrebidjsToOpenRTB(bidderRequest); + let payload = aux.payload; + return { + method: 'POST', + url: ENDPOINT + aux.partnerId, + data: JSON.stringify(payload), + bidderRequest: bidderRequest + }; + }, + + interpretResponse: function(response, request) { + let bidResponses = []; + if (response.body) { + if (!response.body.length) { + response.body = [response.body]; + } + bidResponses = parseResponseOpenRTBToPrebidjs(response.body); + } + return bidResponses; + } +}; + +function parseRequestPrebidjsToOpenRTB(prebidRequest) { + let ret = { + payload: {}, + partnerId: null + }; + let pageURL = window.location.href; + let domain = window.location.hostname; + let secure = (window.location.protocol == 'https:' ? 1 : 0); + let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); + openRTBRequest.id = prebidRequest.auctionId; + openRTBRequest.ext = { + auctionstart: Date.now() + }; + + openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); + openRTBRequest.site.id = domain; + openRTBRequest.site.name = domain; + openRTBRequest.site.domain = domain; + openRTBRequest.site.page = pageURL; + openRTBRequest.site.ref = prebidRequest.refererInfo.referer; + + openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); + openRTBRequest.site.publisher.id = 0; + openRTBRequest.site.publisher.name = config.getConfig('publisherDomain'); + openRTBRequest.site.publisher.domain = domain; + openRTBRequest.site.publisher.domain = domain; + + openRTBRequest.site.content = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSiteContent'])); + + openRTBRequest.source = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSource'])); + openRTBRequest.source.fd = 0; + openRTBRequest.source.tid = prebidRequest.auctionId; + openRTBRequest.source.pchain = ''; + + openRTBRequest.device = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestDevice'])); + + openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); + + openRTBRequest.imp = []; + prebidRequest.bids.forEach(function(bid, impId) { + impId++; + if (!ret.partnerId) { + ret.partnerId = bid.params.partnerId; + } + let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); + imp.id = impId; + imp.secure = secure; + imp.tagid = bid.bidId; + + openRTBRequest.site.publisher.id = openRTBRequest.site.publisher.id || 0; + openRTBRequest.tmax = openRTBRequest.tmax || bid.params.tmax || 0; + + Object.keys(bid.mediaTypes).forEach(function(mediaType) { + if (mediaType == 'banner') { + imp.banner = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImpBanner'])); + imp.banner.w = 0; + imp.banner.h = 0; + imp.banner.format = []; + bid.mediaTypes[mediaType].sizes.forEach(function(adSize) { + if (!imp.banner.w) { + imp.banner.w = adSize[0]; + imp.banner.h = adSize[1]; + } + imp.banner.format.push({w: adSize[0], h: adSize[1]}); + }); + } + }); + openRTBRequest.imp.push(imp); + }); + ret.payload = openRTBRequest; + return ret; +} +function parseResponseOpenRTBToPrebidjs(openRTBResponse) { + let prebidResponse = []; + openRTBResponse.forEach(function(response) { + if (response.seatbid && response.seatbid.forEach) { + response.seatbid.forEach(function(seatbid) { + if (seatbid.bid && seatbid.bid.forEach) { + seatbid.bid.forEach(function(bid) { + let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); + prebid.requestId = bid.ext.tagid; + prebid.ad = bid.adm; + prebid.creativeId = bid.crid; + prebid.cpm = bid.price; + prebid.width = bid.w; + prebid.height = bid.h; + prebid.mediaType = 'banner'; + prebid.meta = { + advertiserDomains: bid.adomain, + advertiserId: bid.adid, + mediaType: 'banner', + primaryCatId: bid.cat[0] || '', + secondaryCatIds: bid.cat + } + prebidResponse.push(prebid); + }); + } + }); + } + }); + return prebidResponse; +} + +registerBidder(spec); diff --git a/modules/interactiveOffersBidAdapter.md b/modules/interactiveOffersBidAdapter.md index 7eb440c8216..b96572fbf94 100644 --- a/modules/interactiveOffersBidAdapter.md +++ b/modules/interactiveOffersBidAdapter.md @@ -1,14 +1,14 @@ # Overview - + ``` Module Name: interactiveOffers Bidder Adapter Module Type: Bidder Adapter -Maintainer: devteam@interactiveoffers.com +Maintainer: dev@interactiveoffers.com ``` # Description -Module that connects to interactiveOffers demand sources. Param pubId is required. +Module that connects to interactiveOffers demand sources. Param partnerId is required. # Test Parameters ``` @@ -24,11 +24,11 @@ Module that connects to interactiveOffers demand sources. Param pubId is require { bidder: "interactiveOffers", params: { - pubId: '10', - tmax: 5000 + partnerId: "abcd1234", + tmax: 250 } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/invamiaBidAdapter.js b/modules/invamiaBidAdapter.js new file mode 100644 index 00000000000..2d36fb77e16 --- /dev/null +++ b/modules/invamiaBidAdapter.js @@ -0,0 +1,92 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'invamia'; +const ENDPOINT_URL = 'https://ad.invamia.com/delivery/impress'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid request params to validate. + * @return boolean True if this is a valid bid request, and false otherwise. + */ + isBidRequestValid: function(bidRequest) { + return !!bidRequest.params.zoneId; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an array of bid requests + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests) { + let serverRequests = []; + + validBidRequests.forEach(bidRequest => { + const sizes = bidRequest.mediaTypes.banner.sizes; + + sizes.forEach(([width, height]) => { + bidRequest.params.requestedSizes = [width, height]; + + const payload = { + ctype: 'div', + pzoneid: bidRequest.params.zoneId, + width, + height, + }; + + const payloadString = Object.keys(payload).map(k => k + '=' + encodeURIComponent(payload[k])).join('&'); + + serverRequests.push({ + method: 'GET', + url: ENDPOINT_URL, + data: payloadString, + bidderRequest: bidRequest, + }); + }); + }); + + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidderRequest A matched bid request for this response. + * @return Array An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + const response = serverResponse.body; + const bidResponses = []; + + if (response && response.template && response.template.html) { + const {bidId} = bidderRequest; + const [width, height] = bidderRequest.params.requestedSizes; + + const bidResponse = { + requestId: bidId, + cpm: response.hb.cpm, + creativeId: response.banner.hash, + currency: 'USD', + netRevenue: response.hb.netRevenue, + ttl: 600, + ad: response.template.html, + mediaType: 'banner', + meta: { + advertiserDomains: response.hb.adomains || [], + }, + width, + height, + }; + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, +} + +registerBidder(spec); diff --git a/modules/invamiaBidAdapter.md b/modules/invamiaBidAdapter.md new file mode 100644 index 00000000000..c8e12808123 --- /dev/null +++ b/modules/invamiaBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Invamia Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@invamia.com +``` + +# Description + +Module that connects to Invamia demand sources. + +# Test Parameters + +``` + const adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [{ + bidder: 'invamia', + params: { + zoneId: 379783, + }, + }], + }]; +``` diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 220aed47e15..18011359a6d 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', @@ -8,9 +8,11 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 2, + PREBID_VERSION: 6, METHOD: 'GET', - INVIBES_VENDOR_ID: 436 + INVIBES_VENDOR_ID: 436, + USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], + META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); @@ -39,9 +41,7 @@ export const spec = { }, getUserSyncs: function (syncOptions) { if (syncOptions.iframeEnabled) { - handlePostMessage(); const syncUrl = buildSyncUrl(); - return { type: 'iframe', url: syncUrl @@ -55,6 +55,8 @@ registerBidder(spec); // some state info is required: cookie info, unique user visit id const topWin = getTopMostWindow(); let invibes = topWin.invibes = topWin.invibes || {}; +invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false]; +invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; let _customUserSync; function isBidRequestValid(bid) { @@ -78,7 +80,7 @@ function isBidRequestValid(bid) { function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; - let _loginId, _customEndpoint; + let _loginId, _customEndpoint, _userId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { @@ -87,44 +89,50 @@ function buildRequest(bidRequests, bidderRequest) { _loginId = _loginId || bidRequest.params.loginId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; + _userId = _userId || bidRequest.userId; }); - invibes.visitId = invibes.visitId || generateRandomId(); + invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); - cookieDomain = detectTopmostCookieDomain(); + invibes.visitId = invibes.visitId || generateRandomId(); invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie'); - invibes.optIn = invibes.optIn || invibes.getCookie('ivOptIn') || readGdprConsent(bidderRequest.gdprConsent); - - initDomainId(invibes.domainOptions); + let lid = initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); - + let userIdModel = getUserIds(_userId); + let bidParamsJson = { + placementIds: _placementIds, + loginId: _loginId, + auctionStartTime: _ivAuctionStart, + bidVersion: CONSTANTS.PREBID_VERSION + }; + if (userIdModel) { + bidParamsJson.userId = userIdModel; + } let data = { location: getDocumentLocation(topWin), videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), - bidParamsJson: JSON.stringify({ - placementIds: _placementIds, - loginId: _loginId, - auctionStartTime: _ivAuctionStart, - bidVersion: CONSTANTS.PREBID_VERSION - }), + bidParamsJson: JSON.stringify(bidParamsJson), capCounts: getCappedCampaignsAsString(), vId: invibes.visitId, width: topWin.innerWidth, height: topWin.innerHeight, - noc: !cookieDomain, oi: invibes.optIn, - kw: keywords + kw: keywords, + purposes: invibes.purposes.toString(), + li: invibes.legitimateInterests.toString(), + + tc: invibes.gdpr_consent }; - if (invibes.dom.id) { - data.lId = invibes.dom.id; + if (lid) { + data.lId = lid; } const parametersToPassForward = 'videoaddebug,advs,bvci,bvid,istop,trybvid,trybvci'.split(','); @@ -141,7 +149,7 @@ function buildRequest(bidRequests, bidderRequest) { method: CONSTANTS.METHOD, url: _customEndpoint || CONSTANTS.BID_ENDPOINT, data: data, - options: { withCredentials: true }, + options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } bidRequests: bidRequests }; @@ -161,12 +169,6 @@ function handleResponse(responseObj, bidRequests) { responseObj = responseObj.body || responseObj; responseObj = responseObj.videoAdContentResult || responseObj; - let bidModel = responseObj.BidModel; - if (typeof bidModel !== 'object') { - utils.logInfo('Invibes Adapter - Bidding is not configured'); - return []; - } - if (typeof invibes.bidResponse === 'object') { utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); return []; @@ -174,51 +176,83 @@ function handleResponse(responseObj, bidRequests) { invibes.bidResponse = responseObj; - let ads = responseObj.Ads; + const bidResponses = []; + for (let i = 0; i < bidRequests.length; i++) { + let bidRequest = bidRequests[i]; + + let requestPlacement = null; + if (responseObj.AdPlacements != null) { + for (let j = 0; j < responseObj.AdPlacements.length; j++) { + let bidModel = responseObj.AdPlacements[j].BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj.AdPlacements[j]; + break; + } + } + } else { + let bidModel = responseObj.BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj; + } + } + + let bid = createBid(bidRequest, requestPlacement); + if (bid !== null) { + bidResponses.push(bid); + } + } + + return bidResponses; +} + +function createBid(bidRequest, requestPlacement) { + if (requestPlacement === null || requestPlacement.BidModel === null) { + utils.logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); + return null; + } + let bidModel = requestPlacement.BidModel; + let ads = requestPlacement.Ads; if (!Array.isArray(ads) || ads.length < 1) { - if (responseObj.AdReason != null) { - utils.logInfo('Invibes Adapter - ' + responseObj.AdReason); + if (requestPlacement.AdReason != null) { + utils.logInfo('Invibes Adapter - No ads ' + requestPlacement.AdReason); } utils.logInfo('Invibes Adapter - No ads available'); - return []; + return null; } let ad = ads[0]; + let size = getBiggerSize(bidRequest.sizes); - if (bidModel.PlacementId == null) { - utils.logInfo('Invibes Adapter - No Placement Id in response'); - return []; - } - - const bidResponses = []; - for (let i = 0; i < bidRequests.length; i++) { - let bidRequest = bidRequests[i]; + const now = Date.now(); + utils.logInfo('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); - if (bidModel.PlacementId == bidRequest.params.placementId) { - let size = getBiggerSize(bidRequest.sizes); - - bidResponses.push({ - requestId: bidRequest.bidId, - cpm: ad.BidPrice, - width: bidModel.Width || size[0], - height: bidModel.Height || size[1], - creativeId: ad.VideoExposedId, - currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, - netRevenue: true, - ttl: CONSTANTS.TIME_TO_LIVE, - ad: renderCreative(bidModel) - }); + return { + requestId: bidRequest.bidId, + cpm: ad.BidPrice, + width: bidModel.Width || size[0], + height: bidModel.Height || size[1], + creativeId: ad.VideoExposedId, + currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, + netRevenue: true, + ttl: CONSTANTS.TIME_TO_LIVE, + ad: renderCreative(bidModel), + meta: addMeta(bidModel.Meta) + }; +} - const now = Date.now(); - ivLogger.info('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); - } else { - utils.logInfo('Invibes Adapter - Incorrect Placement Id: ' + bidRequest.params.placementId); +function addMeta(bidModelMeta) { + var meta = {}; + if (bidModelMeta != null) { + for (let i = 0; i < CONSTANTS.META_TAXONOMY.length; i++) { + if (bidModelMeta.hasOwnProperty(CONSTANTS.META_TAXONOMY[i])) { + meta[CONSTANTS.META_TAXONOMY[i]] = bidModelMeta[CONSTANTS.META_TAXONOMY[i]]; + } } } - return bidResponses; + return meta; } function generateRandomId() { @@ -229,9 +263,26 @@ function getDocumentLocation(topWin) { return topWin.location.href.substring(0, 300).split(/[?#]/)[0]; } +function getUserIds(bidUserId) { + let userId; + if (bidUserId) { + CONSTANTS.USERID_PROVIDERS.forEach(provider => { + if (bidUserId[provider]) { + userId = userId || {}; + userId[provider] = bidUserId[provider]; + } + }); + } + + return userId; +} + function parseQueryStringParams() { let params = {}; - try { params = JSON.parse(localStorage.ivbs); } catch (e) { } + try { + params = JSON.parse(localStorage.ivbs); + } catch (e) { + } let re = /[\\?&]([^=]+)=([^\\?&#]+)/g; let m; while ((m = re.exec(window.location.href)) != null) { @@ -258,9 +309,12 @@ function getTopMostWindow() { try { while (top !== res) { - if (res.parent.location.href.length) { res = res.parent; } + if (res.parent.location.href.length) { + res = res.parent; + } } - } catch (e) { } + } catch (e) { + } return res; } @@ -278,6 +332,10 @@ function renderCreative(bidModel) { function getCappedCampaignsAsString() { const key = 'ivvcap'; + if (!invibes.optIn || !invibes.purposes[0]) { + return ''; + } + let loadData = function () { try { return JSON.parse(storage.getDataFromLocalStorage(key)) || {}; @@ -310,26 +368,22 @@ function getCappedCampaignsAsString() { clearExpired(); let data = loadData(); return Object.keys(data) - .filter(function (k) { return data.hasOwnProperty(k); }) + .filter(function (k) { + return data.hasOwnProperty(k); + }) .sort() - .map(function (k) { return [k, data[k][0]]; }); + .map(function (k) { + return [k, data[k][0]]; + }); }; return getCappedCampaigns() - .map(function (record) { return record.join('='); }) + .map(function (record) { + return record.join('='); + }) .join(','); } -const noop = function () { }; - -function initLogger() { - if (storage.hasLocalStorage() && localStorage.InvibesDEBUG) { - return window.console; - } - - return { info: noop, error: noop, log: noop, warn: noop, debug: noop }; -} - function buildSyncUrl() { let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT; syncUrl += '?visitId=' + invibes.visitId; @@ -348,49 +402,46 @@ function buildSyncUrl() { return syncUrl; } -function handlePostMessage() { - try { - if (window.addEventListener) { - window.addEventListener('message', acceptPostMessage); - } - } catch (e) { } -} - -function acceptPostMessage(e) { - let msg = e.data || {}; - if (msg.ivbscd === 1) { - invibes.setCookie(msg.name, msg.value, msg.exdays, msg.domain); - } else if (msg.ivbscd === 2) { - invibes.dom.graduate(); - } -} - function readGdprConsent(gdprConsent) { if (gdprConsent && gdprConsent.vendorData) { + invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData); + if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { + var index; + for (index = 0; index < invibes.purposes.length; ++index) { + invibes.purposes[index] = true; + } + + for (index = 0; index < invibes.legitimateInterests.length; ++index) { + invibes.legitimateInterests[index] = true; + } return 2; } let purposeConsents = getPurposeConsents(gdprConsent.vendorData); - if (purposeConsents == null) { return 0; } - let properties = Object.keys(purposeConsents); - let purposeConsentsCounter = getPurposeConsentsCounter(gdprConsent.vendorData); - - if (properties.length < purposeConsentsCounter) { + if (purposeConsents == null) { return 0; } + let purposesLength = getPurposeConsentsCounter(gdprConsent.vendorData); - for (let i = 0; i < purposeConsentsCounter; i++) { - if (!purposeConsents[properties[i]] || purposeConsents[properties[i]] === 'false') { return 0; } + if (!tryCopyValueToArray(purposeConsents, invibes.purposes, purposesLength)) { + return 0; } + let legitimateInterests = getLegitimateInterests(gdprConsent.vendorData); + tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, 10); + + let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10); let vendorConsents = getVendorConsents(gdprConsent.vendorData); - if (vendorConsents == null || vendorConsents[CONSTANTS.INVIBES_VENDOR_ID.toString(10)] == null) { + let vendorHasLegitimateInterest = getVendorLegitimateInterest(gdprConsent.vendorData)[invibesVendorId] === true; + if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { return 4; } - if (vendorConsents[CONSTANTS.INVIBES_VENDOR_ID.toString(10)] === false) { return 0; } + if (vendorConsents[invibesVendorId] === false && vendorHasLegitimateInterest === false) { + return 0; + } return 2; } @@ -398,6 +449,37 @@ function readGdprConsent(gdprConsent) { return 0; } +function tryCopyValueToArray(value, target, length) { + if (value instanceof Array) { + for (let i = 0; i < length && i < value.length; i++) { + target[i] = !((value[i] === false || value[i] === 'false' || value[i] == null)); + } + return true; + } + if (typeof value === 'object' && value !== null) { + let i = 0; + for (let prop in value) { + if (i === length) { + break; + } + + if (value.hasOwnProperty(prop)) { + let parsedProp = parseInt(prop); + if (isNaN(parsedProp)) { + target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } else { + target[parsedProp - 1] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } + + i++; + } + } + return true; + } + + return false; +} + function getPurposeConsentsCounter(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { return 10; @@ -418,6 +500,23 @@ function getPurposeConsents(vendorData) { return null; } +function getLegitimateInterests(vendorData) { + if (vendorData.purpose && vendorData.purpose.legitimateInterests) { + return vendorData.purpose.legitimateInterests; + } + + return null; +} + +function getVendorConsentData(vendorData) { + if (vendorData.purpose && vendorData.purpose.consents) { + if (vendorData.tcString != null) { + return vendorData.tcString; + } + } + return vendorData.consentData; +}; + function getVendorConsents(vendorData) { if (vendorData.vendor && vendorData.vendor.consents) { return vendorData.vendor.consents; @@ -430,144 +529,58 @@ function getVendorConsents(vendorData) { return null; } -const ivLogger = initLogger(); +function getVendorLegitimateInterest(vendorData) { + if (vendorData.vendor && vendorData.vendor.legitimateInterests) { + return vendorData.vendor.legitimateInterests; + } + + return {}; +} /// Local domain cookie management ===================== invibes.Uid = { generate: function () { let maxRand = parseInt('zzzzzz', 36) - let mkRand = function () { return Math.floor(Math.random() * maxRand).toString(36); }; + let mkRand = function () { + return Math.floor(Math.random() * maxRand).toString(36); + }; let rand1 = mkRand(); let rand2 = mkRand(); return rand1 + rand2; } }; -let cookieDomain; invibes.getCookie = function (name) { - if (!storage.cookiesAreEnabled()) { return; } - let i, x, y; - let cookies = document.cookie.split(';'); - for (i = 0; i < cookies.length; i++) { - x = cookies[i].substr(0, cookies[i].indexOf('=')); - y = cookies[i].substr(cookies[i].indexOf('=') + 1); - x = x.replace(/^\s+|\s+$/g, ''); - if (x === name) { - return unescape(y); - } + if (!storage.cookiesAreEnabled()) { + return; } -}; - -invibes.setCookie = function (name, value, exdays, domain) { - if (!storage.cookiesAreEnabled()) { return; } - let whiteListed = name == 'ivNoCookie' || name == 'IvbsCampIdsLocal'; - if (invibes.noCookies && !whiteListed && (exdays || 0) >= 0) { return; } - if (exdays > 365) { exdays = 365; } - domain = domain || cookieDomain; - let exdate = new Date(); - let exms = exdays * 24 * 60 * 60 * 1000; - exdate.setTime(exdate.getTime() + exms); - storage.setCookie(name, value, exdate.toUTCString(), undefined, domain); -}; -let detectTopmostCookieDomain = function () { - let testCookie = invibes.Uid.generate(); - let hostParts = location.hostname.split('.'); - if (hostParts.length === 1) { - return location.hostname; - } - for (let i = hostParts.length - 1; i >= 0; i--) { - let domain = '.' + hostParts.slice(i).join('.'); - invibes.setCookie(testCookie, testCookie, 1, domain); - let val = invibes.getCookie(testCookie); - if (val === testCookie) { - invibes.setCookie(testCookie, testCookie, -1, domain); - return domain; - } + if (!invibes.optIn || !invibes.purposes[0]) { + return; } + + return storage.getCookie(name); }; let initDomainId = function (options) { - if (invibes.dom) { return; } - - options = options || {}; - let cookiePersistence = { cname: 'ivbsdid', load: function () { let str = invibes.getCookie(this.cname) || ''; try { return JSON.parse(str); - } catch (e) { } - }, - save: function (obj) { - invibes.setCookie(this.cname, JSON.stringify(obj), 365); - } - }; - - let persistence = options.persistence || cookiePersistence; - let state; - let minHC = 2; - - let validGradTime = function (state) { - if (!state.cr) { return false; } - let min = 151 * 10e9; - if (state.cr < min) { - return false; + } catch (e) { + } } - let now = new Date().getTime(); - let age = now - state.cr; - let minAge = 24 * 60 * 60 * 1000; - return age > minAge; - }; - - state = persistence.load() || { - id: invibes.Uid.generate(), - cr: new Date().getTime(), - hc: 1, }; - if (state.id.match(/\./)) { - state.id = invibes.Uid.generate(); - } - - let graduate = function () { - if (!state.cr) { return; } - delete state.cr; - delete state.hc; - persistence.save(state); - setId(); - }; + options = options || {}; - let regenerateId = function () { - state.id = invibes.Uid.generate(); - persistence.save(state); - }; + var persistence = options.persistence || cookiePersistence; - let setId = function () { - invibes.dom = { - get id() { - return (!state.cr && invibes.optIn > 0) ? state.id : undefined; - }, - get tempId() { - return (invibes.optIn > 0) ? state.id : undefined; - }, - graduate: graduate, - regen: regenerateId - }; - }; + let state = persistence.load(); - if (state.cr && !options.noVisit) { - if (state.hc < minHC) { - state.hc++; - } - if ((state.hc >= minHC && validGradTime(state)) || options.skipGraduation) { - graduate(); - } - } - persistence.save(state); - setId(); - ivLogger.info('Did=' + invibes.dom.id); + return state ? (state.id || state.tempId) : undefined; }; let keywords = (function () { @@ -638,6 +651,7 @@ let keywords = (function () { } return kw; }()); + // ===================== export function resetInvibes() { diff --git a/modules/ipromBidAdapter.md b/modules/ipromBidAdapter.md new file mode 100644 index 00000000000..f7124e7c89c --- /dev/null +++ b/modules/ipromBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Iprom PreBid Adapter +Module Type: Bidder Adapter +Maintainer: support@iprom.si +``` + +# Description + +Module that connects to Iprom's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "iprom", + params: { + id: '1234', + dimension: '300x250' + } + } + ] + } + ]; +``` diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js new file mode 100644 index 00000000000..e7599fd54c0 --- /dev/null +++ b/modules/iqmBidAdapter.js @@ -0,0 +1,290 @@ +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 {INSTREAM} from '../src/video.js'; + +const BIDDER_CODE = 'iqm'; +const VERSION = 'v.1.0.0'; +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay' +]; +var ENDPOINT_URL = 'https://pbd.bids.iqm.com'; + +export const spec = { + supportedMediaTypes: [BANNER, VIDEO], + code: BIDDER_CODE, + aliases: ['iqm'], + + /** + * 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 banner = utils.deepAccess(bid, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if ((videoMediaType && context === INSTREAM)) { + const videoBidderParams = utils.deepAccess(bid, 'params.video', {}); + + if (!Array.isArray(videoMediaType.playerSize)) { + return false; + } + + if (!videoMediaType.context) { + return false; + } + + const videoParams = { + ...videoMediaType, + ...videoBidderParams + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + return false; + } + + if ( + typeof videoParams.placement !== 'undefined' && + typeof videoParams.placement !== 'number' + ) { + return false; + } + if ( + videoMediaType.context === INSTREAM && + typeof videoParams.startdelay !== 'undefined' && + typeof videoParams.startdelay !== 'number' + ) { + return false; + } + + return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); + } else { + if (banner === 'undefined') { + return false; + } + return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); + } + }, + /** + * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + *It prepares a bid request with the required information for the DSP side and sends this request to alloted endpoint + * parameter{validBidRequests, bidderRequest} bidderRequest object is useful because it carries a couple of bid parameters that are global to all the bids. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var finalRequest = {}; + let bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + + const imp = { + id: bid.bidId, + secure: 1, + bidfloor: bidfloor || 0, + displaymanager: 'Prebid.js', + displaymanagerver: VERSION, + + } + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + imp.banner = getSize(bid.sizes); + imp.mediatype = 'banner'; + } else if (utils.deepAccess(bid, 'mediaTypes.video')) { + imp.video = _buildVideoORTB(bid); + imp.mediatype = 'video'; + } + const site = getSite(bid); + let device = getDevice(bid.params); + finalRequest = { + sizes: bid.sizes, + id: bid.bidId, + publisherId: utils.getBidIdParameter('publisherId', bid.params), + placementId: utils.getBidIdParameter('placementId', bid.params), + device: device, + site: site, + imp: imp, + auctionId: bid.auctionId, + adUnitCode: bid.adUnitCode, + bidderRequestId: bid.bidderRequestId, + uuid: bid.bidId, + bidderRequest + } + const request = { + method: 'POST', + url: ENDPOINT_URL, + data: finalRequest, + options: { + withCredentials: false + }, + + } + return request; + }); + }, + /** + * Takes Response from server as input and request. + *It parses the response from server side and generates bidresponses for with required rendering paramteres + * parameter{serverResponse, bidRequest} serverReponse: Response from the server side with ad creative. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && utils.isArray(serverResponse.seatbid)) { + utils._each(serverResponse.seatbid, function (bidList) { + utils._each(bidList.bid, function (bid) { + const responseCPM = parseFloat(bid.price); + if (responseCPM > 0.0 && bid.impid) { + const bidResponse = { + requestId: bidRequest.data.id, + currency: serverResponse.cur || 'USD', + cpm: responseCPM, + netRevenue: true, + creativeId: bid.crid || '', + adUnitCode: bidRequest.data.adUnitCode, + auctionId: bidRequest.data.auctionId, + mediaType: bidRequest.data.imp.mediatype, + + ttl: bid.ttl || config.getConfig('_bidderTimeout') + }; + + if (bidRequest.data.imp.mediatype === VIDEO) { + bidResponse.width = bid.w || bidRequest.data.imp.video.w; + bidResponse.height = bid.h || bidRequest.data.imp.video.h; + bidResponse.adResponse = { + content: bid.adm, + height: bidRequest.data.imp.video.h, + width: bidRequest.data.imp.video.w + }; + + if (bidRequest.data.imp.video.context === INSTREAM) { + bidResponse.vastUrl = bid.adm; + } + } else if (bidRequest.data.imp.mediatype === BANNER) { + bidResponse.ad = bid.adm; + bidResponse.width = bid.w || bidRequest.data.imp.banner.w; + bidResponse.height = bid.h || bidRequest.data.imp.banner.h; + } + bidResponses.push(bidResponse); + } + }) + }); + } + return bidResponses; + }, + +}; + +let getDevice = function (bidparams) { + const language = navigator.language ? 'language' : 'userLanguage'; + return { + geo: bidparams.geo, + h: screen.height, + w: screen.width, + dnt: _getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent, + devicetype: _isMobile() ? 1 : _isConnectedTV() ? 3 : 2 + }; +}; + +let _getDNT = function () { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; +}; + +let getSize = function (sizes) { + let sizeMap; + if (sizes.length === 2 && typeof sizes[0] === 'number' && typeof sizes[1] === 'number') { + sizeMap = {w: sizes[0], h: sizes[1]}; + } else { + sizeMap = {w: sizes[0][0], h: sizes[0][1]}; + } + return sizeMap; +}; + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent); +} + +function getSite(bidderRequest) { + let domain = ''; + let page = ''; + let referrer = ''; + const Id = 1; + + const {refererInfo} = bidderRequest; + + if (canAccessTopWindow()) { + const wt = utils.getWindowTop(); + domain = wt.location.hostname; + page = wt.location.href; + referrer = wt.document.referrer || ''; + } else if (refererInfo.reachedTop) { + const url = utils.parseUrl(refererInfo.referer); + domain = url.hostname; + page = refererInfo.referer; + } else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) { + const url = utils.parseUrl(refererInfo.stack[0]); + domain = url.hostname; + } + + return { + domain, + page, + Id, + referrer + }; +}; + +function canAccessTopWindow() { + try { + if (utils.getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} + +function _buildVideoORTB(bidRequest) { + const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video'); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const video = {} + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + video.context = 1; + const {w, h} = getSize(videoParams.playerSize[0]); + video.w = w; + video.h = h; + + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + video.placement = video.placement || 2; + + video.startdelay = video.startdelay || 0; + video.placement = 1; + video.context = INSTREAM; + + return video; +} +registerBidder(spec); diff --git a/modules/iqmBidAdapter.md b/modules/iqmBidAdapter.md index d6d1b4d037d..85dfdc7e078 100644 --- a/modules/iqmBidAdapter.md +++ b/modules/iqmBidAdapter.md @@ -10,9 +10,8 @@ Maintainer: hbteam@iqm.com | Name | Scope | Description | Example | | :------------ | :------- | :------------------------ | :------------------- | -| `publisherId` | required | The Publisher ID from iQM | "df5fd732-c5f3-11e7" | -| `tagId` | required | The tag ID from iQM | "1c5c9ec2-c5f4-11e7" | -| `placementId` | required | The Placement ID from iQM | "50cc36fe-c5f4-11e7" | +| `publisherId` | required | The Publisher ID from iQM | "df5fd732-c5f3-11e7-abc4-cec278b6b50a" | +| `placementId` | required | The Placement ID from iQM | 23451 | | `bidfloor` | optional | Bid Floor | 0.50 | # Description @@ -21,21 +20,62 @@ Module that connects to iQM demand sources # Test Parameters ``` - var adUnits = [ - { - code: 'test-div1', - sizes: [[320, 50]], // display 320x50 - bids: [ - { - bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - tagId: '1c5c9ec2-c5f4-11e7-abc4-cec278b6b50a', - placementId: '50cc36fe-c5f4-11e7-abc4-cec278b6b50a', - bidfloor: 0.50, - } - } - ] - } - ]; +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300,250]] + } + }, + + bids: [{ + bidder: 'iqm', + params: { + geo:{ + country:'USA' + }, + + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.50 + } + }] + + }] + +``` + +# adUnit Video + +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'iqm', + params: { + // placementId: iosDevice ? 13239390 : 13232361, // Add your own placement id here. Note, skippable video is not supported on iOS + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + geo:{ + country:'USA' + }, + + bidfloor: 0.05, + video: { + placement :2, + mimes: ['video/mp4'], + protocols: [2,5], + skipppable: true, + playback_method: ['auto_play_sound_off'] + } + } + }] + }; + ``` diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js new file mode 100644 index 00000000000..77bdcc0188d --- /dev/null +++ b/modules/iqzoneBidAdapter.js @@ -0,0 +1,176 @@ +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 = 'iqzone'; +const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; + +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 } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + placementId, + bidId, + schain, + bidfloor + }; + + 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 (!utils.isFn(bid.getFloor)) { + return utils.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); + + 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) { + utils.logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + utils.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; + } +}; + +registerBidder(spec); diff --git a/modules/iqzoneBidAdapter.md b/modules/iqzoneBidAdapter.md new file mode 100644 index 00000000000..75e82d59b3e --- /dev/null +++ b/modules/iqzoneBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: IQZone Bidder Adapter +Module Type: IQZone Bidder Adapter +Maintainer: no-reply@vsn.si +``` + +# Description + +Connects to IQZone exchange for bids. + +IQZone 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: 'iqzone', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'iqzone', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'iqzone', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/ironsourceBidAdapter.md b/modules/ironsourceBidAdapter.md new file mode 100644 index 00000000000..86756b08809 --- /dev/null +++ b/modules/ironsourceBidAdapter.md @@ -0,0 +1,51 @@ +#Overview + +Module Name: IronSource Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid-digital-brands@ironsrc.com + + +# Description + +Module that connects to IronSource's demand sources. + +The IronSource adapter requires setup and approval from the IronSource. Please reach out to prebid-digital-brands@ironsrc.com to create an IronSource account. + +The adapter supports Video(instream). For the integration, IronSource 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. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `isOrg` | required | String | IronSource publisher Id provided by your IronSource 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" +| `testMode` | optional | Boolean | This activates the test mode | false + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, + bids: [{ + bidder: 'ironsource', + params: { + isOrg: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + ifa: 'XXX-XXX', // Optional + testMode: false // Optional + } + }] + } + ]; +``` diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 62d4b015aa7..8f821e87911 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -2,11 +2,15 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; -import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'ix'; -const SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const ALIAS_BIDDER_CODE = 'roundel'; +const GLOBAL_VENDOR_ID = 10; +const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BANNER_ENDPOINT_VERSION = 7.2; const VIDEO_ENDPOINT_VERSION = 8.1; @@ -14,25 +18,85 @@ const CENT_TO_DOLLAR_FACTOR = 100; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; +const MAX_REQUEST_SIZE = 8000; +const MAX_REQUEST_LIMIT = 4; + const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; +const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js'; +const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; +// determines which eids we send and the rtiPartner field in ext + +const FIRST_PARTY_DATA = { + SITE: [ + 'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile', + 'privacypolicy', 'publisher', 'content', 'keywords', 'ext' + ], + USER: ['id', 'buyeruid', 'yob', 'gender', 'keywords', 'customdata', 'geo', 'data', 'ext'] +}; + +const SOURCE_RTI_MAPPING = { + 'liveramp.com': 'idl', + 'netid.de': 'NETID', + 'neustar.biz': 'fabrickId', + 'zeotap.com': 'zeotapIdPlus', + 'uidapi.com': 'UID2', + 'adserver.org': 'TDID' +}; + +const PROVIDERS = [ + 'britepoolid', + 'id5id', + 'lipbid', + 'haloId', + 'criteoId', + 'lotamePanoramaId', + 'merkleId', + 'parrableId', + 'connectid', + 'tapadId', + 'quantcastId', + 'pubcid', + 'TDID', + 'flocId' +]; + +const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd + +const VIDEO_PARAMS_ALLOW_LIST = [ + 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', + 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', + 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', + 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext', + 'playerSize', 'w', 'h' +]; /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * - * @param {object} bid A valid bid request config object. + * @param {object} bid A valid bid request config object * @return {object} A impression object that will be sent to ad server. */ function bidToBannerImp(bid) { const imp = bidToImp(bid); - imp.banner = {}; - imp.banner.w = bid.params.size[0]; - imp.banner.h = bid.params.size[1]; + const impSize = utils.deepAccess(bid, 'params.size'); + if (impSize) { + imp.banner.w = impSize[0]; + imp.banner.h = impSize[1]; + // populate sid with size if not id + if (!utils.deepAccess(imp, 'ext.sid')) { + imp.ext.sid = utils.parseGPTSingleSizeArray(impSize); + } + } + imp.banner.topframe = utils.inIframe() ? 0 : 1; + _applyFloor(bid, imp, BANNER); + return imp; } @@ -44,25 +108,65 @@ function bidToBannerImp(bid) { */ function bidToVideoImp(bid) { const imp = bidToImp(bid); + const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); + const videoParamRef = utils.deepAccess(bid, 'params.video'); + const videoParamErrors = checkVideoParams(videoAdUnitRef, videoParamRef); + if (videoParamErrors.length) { + return {}; + } + + imp.video = videoParamRef ? utils.deepClone(bid.params.video) : {}; - imp.video = utils.deepClone(bid.params.video) - imp.video.w = bid.params.size[0]; - imp.video.h = bid.params.size[1]; + // copy all video properties to imp object + for (const adUnitProperty in videoAdUnitRef) { + if (VIDEO_PARAMS_ALLOW_LIST.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { + imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; + } + } + + if (imp.video.minduration > imp.video.maxduration) { + utils.logError(`IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`); + return {}; + } - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - if (context) { - if (context === 'instream') { + const context = (videoParamRef && videoParamRef.context) || (videoAdUnitRef && videoAdUnitRef.context); + + // if placement not already defined, pick one based on `context` + if (context && !imp.video.hasOwnProperty('placement')) { + if (context === INSTREAM) { imp.video.placement = 1; - } else if (context === 'outstream') { + } else if (context === OUTSTREAM) { imp.video.placement = 4; } else { - utils.logWarn(`ix bidder params: video context '${context}' is not supported`); + utils.logWarn(`IX Bid Adapter: Video context '${context}' is not supported`); + } + } + + if (!(imp.video.w && imp.video.h)) { + // Getting impression Size + const impSize = getFirstSize(utils.deepAccess(imp, 'video.playerSize')) || getFirstSize(utils.deepAccess(bid, 'params.size')); + if (impSize) { + imp.video.w = impSize[0]; + imp.video.h = impSize[1]; + if (!(utils.deepAccess(imp, 'ext.sid'))) { + imp.ext.sid = utils.parseGPTSingleSizeArray(impSize); + } + } else { + utils.logWarn('IX Bid Adapter: Video size is missing in [mediaTypes.video] missing'); + return {}; } } + _applyFloor(bid, imp, VIDEO); + return imp; } +/** + * Converts an incoming PBJS bid to an IX Impression + * @param {object} bid PBJS bid object + * @returns {object} IX impression object + */ function bidToImp(bid) { const imp = {}; @@ -74,18 +178,62 @@ function bidToImp(bid) { if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { imp.ext.sid = String(bid.params.id); - } else { - imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; } - if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { - imp.bidfloor = bid.params.bidFloor; - imp.bidfloorcur = bid.params.bidFloorCur; + const dfpAdUnitCode = utils.deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); + if (dfpAdUnitCode) { + imp.ext.dfp_ad_unit_code = dfpAdUnitCode; } - return imp; } +/** + * Gets priceFloors floors and IX adapter floors, + * Validates and sets the higher one on the impression + * @param {object} bid bid object + * @param {object} imp impression object + * @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES + */ +function _applyFloor(bid, imp, mediaType) { + let adapterFloor = null; + let moduleFloor = null; + + if (bid.params.bidFloor && bid.params.bidFloorCur) { + adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur }; + } + + if (utils.isFn(bid.getFloor)) { + let _mediaType = '*'; + let _size = '*'; + + if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + try { + moduleFloor = bid.getFloor({ + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + utils.logWarn('priceFloors module call getFloor failed, error : ', err); + } + } + + // Prioritize module floor over bidder.param floor + if (moduleFloor) { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } else if (adapterFloor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } +} + /** * Parses a raw bid for the relevant information. * @@ -95,6 +243,8 @@ function bidToImp(bid) { */ function parseBid(rawBid, currency, bidRequest) { const bid = {}; + const isValidExpiry = !!((utils.deepAccess(rawBid, 'exp') && utils.isInteger(rawBid.exp))); + const dealID = utils.deepAccess(rawBid, 'dealid') || utils.deepAccess(rawBid, 'ext.dealid'); if (PRICE_TO_DOLLAR_FACTOR.hasOwnProperty(currency)) { bid.cpm = rawBid.price / PRICE_TO_DOLLAR_FACTOR[currency]; @@ -104,7 +254,10 @@ function parseBid(rawBid, currency, bidRequest) { bid.requestId = rawBid.impid; - bid.dealId = utils.deepAccess(rawBid, 'ext.dealid'); + if (dealID) { + bid.dealId = dealID; + } + bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; @@ -115,19 +268,23 @@ function parseBid(rawBid, currency, bidRequest) { bid.width = bidRequest.video.w; bid.height = bidRequest.video.h; bid.mediaType = VIDEO; - bid.ttl = VIDEO_TIME_TO_LIVE; + bid.mediaTypes = bidRequest.mediaTypes; + bid.ttl = isValidExpiry ? rawBid.exp : VIDEO_TIME_TO_LIVE; } else { bid.ad = rawBid.adm; bid.width = rawBid.w; bid.height = rawBid.h; bid.mediaType = BANNER; - bid.ttl = BANNER_TIME_TO_LIVE; + bid.ttl = isValidExpiry ? rawBid.exp : BANNER_TIME_TO_LIVE; } bid.meta = {}; bid.meta.networkId = utils.deepAccess(rawBid, 'ext.dspid'); bid.meta.brandId = utils.deepAccess(rawBid, 'ext.advbrandid'); bid.meta.brandName = utils.deepAccess(rawBid, 'ext.advbrand'); + if (rawBid.adomain && rawBid.adomain.length > 0) { + bid.meta.advertiserDomains = rawBid.adomain; + } return bid; } @@ -139,7 +296,7 @@ function parseBid(rawBid, currency, bidRequest) { * @return {boolean} True if this is a valid size format, and false otherwise. */ function isValidSize(size) { - return Array.isArray(size) && size.length === 2 && isInteger(size[0]) && isInteger(size[1]); + return Array.isArray(size) && size.length === 2 && utils.isInteger(size[0]) && utils.isInteger(size[1]); } /** @@ -151,16 +308,65 @@ function isValidSize(size) { * @return {boolean} True if the size object is an element of the size array, and false * otherwise. */ -function includesSize(sizeArray, size) { +function includesSize(sizeArray = [], size = []) { if (isValidSize(sizeArray)) { return sizeArray[0] === size[0] && sizeArray[1] === size[1]; } - for (let i = 0; i < sizeArray.length; i++) { if (sizeArray[i][0] === size[0] && sizeArray[i][1] === size[1]) { return true; } } + return false; +} + +/** + * Checks if all required video params are present + * @param {object} mediaTypeVideoRef Ad unit level mediaTypes object + * @param {object} paramsVideoRef IX bidder params level video object + * @returns {string[]} Are the required video params available + */ +function checkVideoParams(mediaTypeVideoRef, paramsVideoRef) { + const errorList = []; + + if (!mediaTypeVideoRef) { + utils.logWarn('IX Bid Adapter: mediaTypes.video is the preferred location for video params in ad unit'); + } + + for (let property of REQUIRED_VIDEO_PARAMS) { + const propInMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty(property); + const propInVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty(property); + + if (!propInMediaType && !propInVideoRef) { + errorList.push(`IX Bid Adapter: ${property} is not included in either the adunit or params level`); + } + } + + // check protocols/protocol + const protocolMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocol'); + const protocolsMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocols'); + const protocolVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocol'); + const protocolsVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocols'); + + if (!(protocolMediaType || protocolsMediaType || protocolVideoRef || protocolsVideoRef)) { + errorList.push('IX Bid Adapter: protocol/protcols is not included in either the adunit or params level'); + } + + return errorList; +} + +/** + * Get One size from Size Array + * [[250,350]] -> [250, 350] + * [250, 350] -> [250, 350] + * @param {array} sizes array of sizes + */ +function getFirstSize(sizes = []) { + if (isValidSize(sizes)) { + return sizes; + } else if (isValidSize(sizes[0])) { + return sizes[0]; + } return false; } @@ -168,9 +374,9 @@ function includesSize(sizeArray, size) { /** * Determines whether or not the given bidFloor parameters are valid. * - * @param {*} bidFloor The bidFloor parameter inside bid request config. - * @param {*} bidFloorCur The bidFloorCur parameter inside bid request config. - * @return {boolean} True if this is a valid bidFloor parameters format, and false + * @param {number} bidFloor The bidFloor parameter inside bid request config. + * @param {number} bidFloorCur The bidFloorCur parameter inside bid request config. + * @return {bool} True if this is a valid bidFloor parameters format, and false * otherwise. */ function isValidBidFloorParams(bidFloor, bidFloorCur) { @@ -181,78 +387,75 @@ function isValidBidFloorParams(bidFloor, bidFloorCur) { } /** - * Finds the impression with the associated id. + * Get bid request object with the associated id. * * @param {*} id Id of the impression. * @param {array} impressions List of impressions sent in the request. * @return {object} The impression with the associated id. */ -function getBidRequest(id, impressions) { +function getBidRequest(id, impressions, validBidRequests) { if (!id) { return; } - return find(impressions, imp => imp.id === id); + const bidRequest = { + ...find(validBidRequests, bid => bid.bidId === id), + ...find(impressions, imp => imp.id === id) + } + + return bidRequest; } /** - * Adds a User ID module's response into user Eids array. - * - * @param {array} userEids An array of objects containing user ids, - * will be attached to bid request later. - * @param {object} seenIdPartners An object with Identity partners names already added, - * updated with new partner name. - * @param {*} id The id obtained from User ID module. - * @param {string} source The URL of the User ID module. - * @param {string} ixlPartnerName The name of the Identity Partner in IX Library. - * @param {string} rtiPartner The name of the User ID provider in Prebid. - * @return {boolean} True if successfully added the ID to the userEids, false otherwise. + * From the userIdAsEids array, filter for the ones our adserver can use, and modify them + * for our purposes, e.g. add rtiPartner + * @param {array} allEids userIdAsEids passed in by prebid + * @param {object} flocId flocId passed in by prebid + * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter + * identity info from IX Library) */ -function addUserEids(userEids, seenIdPartners, id, source, ixlPartnerName, rtiPartner) { - if (id) { - // mark the partnername that IX RTI uses - seenIdPartners[ixlPartnerName] = 1; - userEids.push({ - source: source, - uids: [{ - id: id, - ext: { - rtiPartner: rtiPartner - } - }] - }); - return true; +function getEidInfo(allEids, flocData) { + let toSend = []; + let seenSources = {}; + if (utils.isArray(allEids)) { + for (const eid of allEids) { + if (SOURCE_RTI_MAPPING[eid.source] && utils.deepAccess(eid, 'uids.0')) { + seenSources[eid.source] = true; + eid.uids[0].ext = { + rtiPartner: SOURCE_RTI_MAPPING[eid.source] + }; + delete eid.uids[0].atype; + toSend.push(eid); + } + } + } + const isValidFlocId = flocData && flocData.id && flocData.version; + if (isValidFlocId) { + const flocEid = { + 'source': 'chrome.com', + 'uids': [{ 'id': flocData.id, 'ext': { 'rtiPartner': 'flocId', 'ver': flocData.version } }] + }; + toSend.push(flocEid); + seenSources['chrome.com'] = true; } - utils.logWarn('Tried to add a user ID from Prebid, the ID received was null'); - return false; + return { toSend, seenSources }; } - /** * Builds a request object to be sent to the ad server based on bid requests. * * @param {array} validBidRequests A list of valid bid request config objects. * @param {object} bidderRequest An object containing other info like gdprConsent. - * @param {array} impressions List of impression objects describing the bids. + * @param {object} impressions An object containing a list of impression objects describing the bids for each transactionId * @param {array} version Endpoint version denoting banner or video. - * @return {object} Info describing the request to the server. + * @return {array} List of objects describing the request to the server. * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { - const userEids = []; - // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; - - // Dict for identity partners already populated from prebid - let seenIdPartners = {}; - // Get ids from Prebid User ID Modules - const userId = validBidRequests[0].userId; - if (userId && typeof userId === 'object') { - if (userId.idl_env) { - addUserEids(userEids, seenIdPartners, userId.idl_env, 'liveramp.com', 'LiveRampIp', 'idl'); - } - } + let eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids'), utils.deepAccess(validBidRequests, '0.userId.flocId')); + let userEids = eidInfo.toSend; // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -261,27 +464,38 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (identityInfo && typeof identityInfo === 'object') { for (const partnerName in identityInfo) { if (identityInfo.hasOwnProperty(partnerName)) { - // check if not already populated by prebid cache - if (!seenIdPartners.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { - userEids.push(response.data); - } + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + userEids.push(response.data); } } } } } - const r = {}; - // Since bidderRequestId are the same for different bid request, just use the first one. - r.id = validBidRequests[0].bidderRequestId; + // If `roundel` alias bidder, only send requests if liveramp ids exist. + if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { + return []; + } - r.imp = impressions; + const r = {}; + // Since bidderRequestId are the same for different bid request, just use the first one. + r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; r.ext = {}; r.ext.source = 'prebid'; + r.ext.ixdiag = {}; + r.ext.ixdiag.msd = 0; + r.ext.ixdiag.msi = 0; + r.imp = []; + + // getting ixdiags for adunits of the video, outstream & multi format (MF) style + let ixdiag = buildIXDiag(validBidRequests); + for (var key in ixdiag) { + r.ext.ixdiag[key] = ixdiag[key]; + } // if an schain is provided, send it along if (validBidRequests[0].schain) { @@ -319,6 +533,12 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.user.ext = { consent: gdprConsent.consentString || '' }; + + if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { + r.user.ext.consented_providers_settings = { + consented_providers: gdprConsent.addtlConsent + } + } } } @@ -331,11 +551,38 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } } + if (config.getConfig('coppa')) { + utils.deepSetValue(r, 'regs.coppa', 1); + } + const payload = {}; + // Use the siteId in the first bid request as the main siteId. + payload.s = validBidRequests[0].params.siteId; + payload.v = version; + payload.ac = 'j'; + payload.sd = 1; + + if (version === VIDEO_ENDPOINT_VERSION) { + payload.nf = 1; + } // Parse additional runtime configs. const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); + const requests = []; + let requestSequenceNumber = 0; + const transactionIds = Object.keys(impressions); + const baseRequestSize = `${baseUrl}${utils.parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; + + if (baseRequestSize > MAX_REQUEST_SIZE) { + utils.logError('ix bidder: Base request size has exceeded maximum request size.'); + return requests; + } + + let currentRequestSize = baseRequestSize; + let fpdRequestSize = 0; + let isFpdAdded = false; + if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. if (typeof otherIxConfig.firstPartyData === 'object') { @@ -348,48 +595,300 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } firstPartyString = firstPartyString.slice(0, -1); - r.site.page += firstPartyString; + fpdRequestSize = encodeURIComponent(firstPartyString).length; + + if (fpdRequestSize < MAX_REQUEST_SIZE) { + if ('page' in r.site) { + r.site.page += firstPartyString; + } else { + r.site.page = firstPartyString; + } + currentRequestSize += fpdRequestSize; + } else { + utils.logError('ix bidder: IX config FPD request size has exceeded maximum request size.'); + } } // Create t in payload if timeout is configured. if (typeof otherIxConfig.timeout === 'number') { payload.t = otherIxConfig.timeout; } + + if (typeof otherIxConfig.detectMissingSizes === 'boolean') { + r.ext.ixdiag.dms = otherIxConfig.detectMissingSizes; + } else { + r.ext.ixdiag.dms = true; + } } - // Use the siteId in the first bid request as the main siteId. - payload.s = validBidRequests[0].params.siteId; - payload.v = version; - payload.r = JSON.stringify(r); - payload.ac = 'j'; - payload.sd = 1; - if (version === VIDEO_ENDPOINT_VERSION) { - payload.nf = 1; + for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { + if (currentRequestSize >= MAX_REQUEST_SIZE || requests.length >= MAX_REQUEST_LIMIT) { + break; + } + + const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; + const { missingCount = 0, missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; + let wasAdUnitImpressionsTrimmed = false; + let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; + const sourceImpressions = { ixImps, missingBannerImpressions }; + const impressionObjects = Object.keys(sourceImpressions) + .map((key) => sourceImpressions[key]) + .filter(item => Array.isArray(item)) + .reduce((acc, curr) => acc.concat(...curr), []); + + let currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + + while (currentImpressionSize > remainingRequestSize) { + wasAdUnitImpressionsTrimmed = true; + impressionObjects.pop(); + currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + } + + if (impressionObjects.length && BANNER in impressionObjects[0]) { + const { id, banner: { topframe } } = impressionObjects[0]; + const _bannerImpression = { + id, + banner: { + topframe, + format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) + }, + } + + if ('bidfloor' in impressionObjects[0]) { + _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + } + + if ('bidfloorcur' in impressionObjects[0]) { + _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + } + + r.imp.push(_bannerImpression); + r.ext.ixdiag.msd += missingCount; + r.ext.ixdiag.msi += missingBannerImpressions.length; + } else { + r.imp.push(...impressionObjects); + } + + currentRequestSize += currentImpressionSize; + + const fpd = config.getConfig('ortb2') || {}; + + if (!utils.isEmpty(fpd) && !isFpdAdded) { + r.ext.ixdiag.fpd = true; + + const site = { ...(fpd.site || fpd.context) }; + + Object.keys(site).forEach(key => { + if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { + delete site[key]; + } + }); + + const user = { ...fpd.user }; + + Object.keys(user).forEach(key => { + if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { + delete user[key]; + } + }); + + const clonedRObject = utils.deepClone(r); + + clonedRObject.site = utils.mergeDeep({}, clonedRObject.site, site); + clonedRObject.user = utils.mergeDeep({}, clonedRObject.user, user); + + const requestSize = `${baseUrl}${utils.parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + + if (requestSize < MAX_REQUEST_SIZE) { + r.site = utils.mergeDeep({}, r.site, site); + r.user = utils.mergeDeep({}, r.user, user); + isFpdAdded = true; + const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; + currentRequestSize += fpdRequestSize; + } else { + utils.logError('ix bidder: FPD request size has exceeded maximum request size.'); + } + } + + const isLastAdUnit = adUnitIndex === transactionIds.length - 1; + + if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { + const clonedPayload = utils.deepClone(payload); + if (!isLastAdUnit || requestSequenceNumber) { + r.ext.ixdiag.sn = requestSequenceNumber; + clonedPayload.sn = requestSequenceNumber; + } + + requestSequenceNumber++; + clonedPayload.r = JSON.stringify(r); + + requests.push({ + method: 'GET', + url: baseUrl, + data: clonedPayload, + validBidRequests + }); + + currentRequestSize = baseRequestSize; + r.imp = []; + r.ext.ixdiag.msd = 0; + r.ext.ixdiag.msi = 0; + isFpdAdded = false; + } } - return { - method: 'GET', - url: baseUrl, - data: payload + return requests; +} + +/** + * Return an object of user IDs stored by Prebid User ID module + * + * @returns {array} ID providers that are present in userIds + */ +function _getUserIds(bidRequest) { + const userIds = bidRequest.userId || {}; + + return PROVIDERS.filter(provider => userIds[provider]); +} + +/** + * Calculates IX diagnostics values and packages them into an object + * + * @param {array} validBidRequests The valid bid requests from prebid + * @return {Object} IX diag values for ad units + */ +function buildIXDiag(validBidRequests) { + var adUnitMap = validBidRequests + .map(bidRequest => bidRequest.transactionId) + .filter((value, index, arr) => arr.indexOf(value) === index) + + var ixdiag = { + mfu: 0, + bu: 0, + iu: 0, + nu: 0, + ou: 0, + allu: 0, + ren: false, + version: '$prebid.version$', + userIds: _getUserIds(validBidRequests[0]) }; + + // create ad unit map and collect the required diag properties + for (let i = 0; i < adUnitMap.length; i++) { + var bid = validBidRequests.filter(bidRequest => bidRequest.transactionId === adUnitMap[i])[0]; + + if (utils.deepAccess(bid, 'mediaTypes')) { + if (Object.keys(bid.mediaTypes).length > 1) { + ixdiag.mfu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.native')) { + ixdiag.nu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + ixdiag.bu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + ixdiag.ou++; + // renderer only needed for outstream + + const hasRenderer = typeof (utils.deepAccess(bid, 'renderer') || utils.deepAccess(bid, 'mediaTypes.video.renderer')) === 'object'; + + // if any one ad unit is missing renderer, set ren status to false in diag + ixdiag.ren = ixdiag.ren && hasRenderer ? (utils.deepAccess(ixdiag, 'ren')) : hasRenderer; + } + + if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'instream') { + ixdiag.iu++; + } + + ixdiag.allu++; + } + } + + return ixdiag; } /** * * @param {array} bannerSizeList list of banner sizes * @param {array} bannerSize the size to be removed - * @return {boolean} true if succesfully removed, false if not found + * @return {boolean} true if successfully removed, false if not found */ function removeFromSizes(bannerSizeList, bannerSize) { + if (!bannerSize) return; + for (let i = 0; i < bannerSizeList.length; i++) { - if (bannerSize[0] == bannerSizeList[i][0] && bannerSize[1] == bannerSizeList[i][1]) { + const size = bannerSizeList[i]; + if (bannerSize[0] === size[0] && bannerSize[1] === size[1]) { bannerSizeList.splice(i, 1); - return true; + break; } } - // size not found - return false; +} + +/** + * Creates IX Video impressions based on validBidRequests + * @param {object} validBidRequest valid request provided by prebid + * @param {object} videoImps reference to created video impressions + */ +function createVideoImps(validBidRequest, videoImps) { + const imp = bidToVideoImp(validBidRequest); + if (Object.keys(imp).length != 0) { + videoImps[validBidRequest.transactionId] = {}; + videoImps[validBidRequest.transactionId].ixImps = []; + videoImps[validBidRequest.transactionId].ixImps.push(imp); + } +} + +/** + * Creates IX banner impressions based on validBidRequests + * @param {object} validBidRequest valid request provided by prebid + * @param {object} missingBannerSizes reference to missing banner config sizes + * @param {object} bannerImps reference to created banner impressions + */ +function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { + const DEFAULT_IX_CONFIG = { + detectMissingSizes: true, + }; + + const ixConfig = { ...DEFAULT_IX_CONFIG, ...config.getConfig('ix') }; + + let imp = bidToBannerImp(validBidRequest); + + const bannerSizeDefined = includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), utils.deepAccess(validBidRequest, 'params.size')); + + // Create IX imps from params.size + if (bannerSizeDefined) { + if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { + bannerImps[validBidRequest.transactionId] = {}; + } + if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { + bannerImps[validBidRequest.transactionId].ixImps = [] + } + bannerImps[validBidRequest.transactionId].ixImps.push(imp); + } + + if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) { + updateMissingSizes(validBidRequest, missingBannerSizes, imp); + } +} + +/** + * Determines IX configuration type based on IX params + * @param {object} valid IX configured param + * @returns {string} + */ +function detectParamsType(validBidRequest) { + if (utils.deepAccess(validBidRequest, 'params.video') && utils.deepAccess(validBidRequest, 'mediaTypes.video')) { + return VIDEO; + } + + return BANNER; } /** @@ -413,7 +912,8 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { if (utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes')) { let sizeList = utils.deepClone(validBidRequest.mediaTypes.banner.sizes); removeFromSizes(sizeList, validBidRequest.params.size); - let newAdUnitEntry = { 'missingSizes': sizeList, + let newAdUnitEntry = { + 'missingSizes': sizeList, 'impression': imp }; missingBannerSizes[transactionID] = newAdUnitEntry; @@ -422,23 +922,68 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { } /** - * + * @param {object} bid ValidBidRequest object, used to adjust floor * @param {object} imp Impression object to be modified * @param {array} newSize The new size to be applied * @return {object} newImp Updated impression object */ -function createMissingBannerImp(imp, newSize) { +function createMissingBannerImp(bid, imp, newSize) { const newImp = utils.deepClone(imp); - newImp.ext.sid = `${newSize[0]}x${newSize[1]}`; + newImp.ext.sid = utils.parseGPTSingleSizeArray(newSize); newImp.banner.w = newSize[0]; newImp.banner.h = newSize[1]; + + _applyFloor(bid, newImp, BANNER); + return newImp; } +/** + * Initialize Outstream Renderer + * @param {Object} bid + */ +function outstreamRenderer (bid) { + bid.renderer.push(() => { + var config = { + width: bid.width, + height: bid.height, + timeout: 3000 + }; + + window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + }); +} + +/** + * Create Outstream Renderer + * @param {string} id + * @returns {Renderer} + */ +function createRenderer (id) { + const renderer = Renderer.install({ + id: id, + url: RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRenderer); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + export const spec = { code: BIDDER_CODE, - gvlid: 10, + gvlid: GLOBAL_VENDOR_ID, + aliases: [{ + code: ALIAS_BIDDER_CODE, + gvlid: GLOBAL_VENDOR_ID, + skipPbsAliasing: false + }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -448,22 +993,36 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!isValidSize(bid.params.size)) { - utils.logError('ix bidder params: bid size has invalid format.'); - return false; - } + const paramsVideoRef = utils.deepAccess(bid, 'params.video'); + const paramsSize = utils.deepAccess(bid, 'params.size'); + const mediaTypeBannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + const mediaTypeVideoRef = utils.deepAccess(bid, 'mediaTypes.video'); + const mediaTypeVideoPlayerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); + const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); - if (!includesSize(bid.sizes, bid.params.size)) { - utils.logError('ix bidder params: bid size is not included in ad unit sizes.'); + if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { return false; } - if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { + if (utils.deepAccess(bid, 'mediaTypes.banner') && !mediaTypeBannerSizes) { return false; } - if (bid.hasOwnProperty('mediaTypes') && !(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize'))) { - return false; + if (paramsSize) { + // since there is an ix bidder level size, make sure its valid + const ixSize = getFirstSize(paramsSize); + if (!ixSize) { + utils.logError('ix bidder params: size has invalid format.'); + return false; + } + // check if the ix bidder level size, is present in ad unit level + if (!includesSize(bid.sizes, ixSize) && + !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && + !(includesSize(mediaTypeBannerSizes, ixSize))) { + utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); + return false; + } } if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { @@ -471,9 +1030,6 @@ export const spec = { return false; } - const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); - const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); - if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { utils.logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); @@ -481,6 +1037,15 @@ export const spec = { } } + if (mediaTypeVideoRef && paramsVideoRef) { + const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef); + if (errorList.length) { + errorList.forEach((err) => { + utils.logError(err); + }); + return false; + } + } return true; }, @@ -492,48 +1057,70 @@ export const spec = { * @return {object} Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let reqs = []; - let bannerImps = []; - let videoImps = []; - let validBidRequest = null; - - // To capture the missing sizes i.e not configured for ix - let missingBannerSizes = {}; + const reqs = []; // Stores banner + video requests + const bannerImps = {}; // Stores created banner impressions + const videoImps = {}; // Stores created video impressions + const multiFormatAdUnits = {}; // Stores references identified multi-format adUnits + const missingBannerSizes = {}; // To capture the missing sizes i.e not configured for ix + + // Step 1: Create impresssions from IX params + validBidRequests.forEach((validBidRequest) => { + const adUnitMediaTypes = Object.keys(utils.deepAccess(validBidRequest, 'mediaTypes', {})) + + switch (detectParamsType(validBidRequest)) { + case BANNER: + createBannerImps(validBidRequest, missingBannerSizes, bannerImps); + break; + case VIDEO: + createVideoImps(validBidRequest, videoImps) + break; + } - for (let i = 0; i < validBidRequests.length; i++) { - validBidRequest = validBidRequests[i]; + if (includes(adUnitMediaTypes, BANNER) && includes(adUnitMediaTypes, VIDEO)) { + multiFormatAdUnits[validBidRequest.transactionId] = validBidRequest; + } + }); - if (validBidRequest.mediaType === VIDEO || utils.deepAccess(validBidRequest, 'mediaTypes.video')) { - if (validBidRequest.mediaType === VIDEO || includesSize(validBidRequest.mediaTypes.video.playerSize, validBidRequest.params.size)) { - videoImps.push(bidToVideoImp(validBidRequest)); - } else { - utils.logError('Bid size is not included in video playerSize') - } + // Step 2: Create impressions for multi-format adunits missing configurations + Object.keys(multiFormatAdUnits).forEach((transactionId) => { + const validBidRequest = multiFormatAdUnits[transactionId]; + if (!bannerImps[transactionId]) { + createBannerImps(validBidRequest, missingBannerSizes, bannerImps); } - if (validBidRequest.mediaType === BANNER || utils.deepAccess(validBidRequest, 'mediaTypes.banner') || - (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { - let imp = bidToBannerImp(validBidRequest); - bannerImps.push(imp); - updateMissingSizes(validBidRequest, missingBannerSizes, imp); + + if (!videoImps[transactionId]) { + createVideoImps(validBidRequest, videoImps) } - } - // Finding the missing banner sizes ,and making impressions for them - for (var transactionID in missingBannerSizes) { - if (missingBannerSizes.hasOwnProperty(transactionID)) { - let missingSizes = missingBannerSizes[transactionID].missingSizes; + }); + + // Step 3: Update banner impressions with missing sizes + for (var transactionId in missingBannerSizes) { + if (missingBannerSizes.hasOwnProperty(transactionId)) { + let missingSizes = missingBannerSizes[transactionId].missingSizes; + + if (!bannerImps.hasOwnProperty(transactionId)) { + bannerImps[transactionId] = {}; + } + if (!bannerImps[transactionId].hasOwnProperty('missingImps')) { + bannerImps[transactionId].missingImps = []; + bannerImps[transactionId].missingCount = 0; + } + + let origImp = missingBannerSizes[transactionId].impression; for (let i = 0; i < missingSizes.length; i++) { - let origImp = missingBannerSizes[transactionID].impression; - let newImp = createMissingBannerImp(origImp, missingSizes[i]); - bannerImps.push(newImp); + let newImp = createMissingBannerImp(validBidRequests[0], origImp, missingSizes[i]); + bannerImps[transactionId].missingImps.push(newImp); + bannerImps[transactionId].missingCount++; } } } - if (bannerImps.length > 0) { - reqs.push(buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); + // Step 4: Build banner & video requests + if (Object.keys(bannerImps).length > 0) { + reqs.push(...buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); } - if (videoImps.length > 0) { - reqs.push(buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); + if (Object.keys(videoImps).length > 0) { + reqs.push(...buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); } return reqs; @@ -566,8 +1153,13 @@ export const spec = { let requestBid = JSON.parse(bidderRequest.data.r); for (let j = 0; j < innerBids.length; j++) { - const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp); + const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp, bidderRequest.validBidRequests); bid = parseBid(innerBids[j], responseBody.cur, bidRequest); + + if (!utils.deepAccess(bid, 'mediaTypes.video.renderer') && utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + bid.mediaTypes.video.renderer = createRenderer(innerBids[j].bidId); + } + bids.push(bid); } } @@ -581,7 +1173,7 @@ export const spec = { * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol * @return {Object} params bid params */ - transformBidParams: function(params, isOpenRtb) { + transformBidParams: function (params, isOpenRtb) { return utils.convertTypes({ 'siteID': 'number' }, params); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index b5cb0d9d2c1..59b699bad2d 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -13,30 +13,10 @@ Description This module connects publishers to Index Exchange's (IX) network of demand sources through Prebid.js. This module is GDPR and CCPA compliant. -It is compatible with both the older ad unit format where the `sizes` and -`mediaType` properties are placed at the top-level of the ad unit, and the newer -format where this information is encapsulated within the `mediaTypes` object. We -recommend that you use the newer format when possible as it will be better able +It is compatible with the newer PrebidJS 5.0 ad unit format where the `banner` and/or `video` properties are encapsulated within the `adUnits[].mediaTypes` object. We +recommend that you use this newer format when possible as it will be better able to accommodate new feature additions. -If a mix of properties from both formats are present within an ad unit, the -newer format's properties will take precedence. - -Here are examples of both formats. - -##### Older Format -```javascript -var adUnits = [{ - // ... - - sizes: [ - [300, 250], - [300, 600] - ] - - // ... -}]; -``` ##### Newer Format ```javascript @@ -51,10 +31,7 @@ var adUnits = [{ }, video: { context: 'instream', - playerSize: [ - [300, 250], - [300, 600] - ] + playerSize: [300, 250] } }, // ... @@ -69,30 +46,59 @@ var adUnits = [{ | Video | Fully supported for all IX approved sizes. | Native | Not supported. -# Bid Parameters + + +# Ad unit or Bidder Parameters + +These params can be specified in the ad unit level `adUnits[].mediaTypes`, which will be the preferred way going forward with PBJS 5.0 Each of the IX-specific parameters provided under the `adUnits[].bids[].params` object are detailed here. + ### Banner | Key | Scope | Type | Description | --- | --- | --- | --- -| siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` -| size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` +| siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` +| sizes | Required | Number[Number[]] | The size / sizes associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` ### Video | Key | Scope | Type | Description | --- | --- | --- | --- -| siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` -| size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` -| video | Required | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. +| siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size is provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` +| size | Optional (Deprecated)| Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` +| video | Optional | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. +|video.w| Required | Integer | The video player size width in pixels that will be passed to demand partners. +|video.h| Required | Integer | The video player size height in pixels that will be passed to demand partners. +|video.playerSize| Optional* | Integer | The video player size that will be passed to demand partners. * In the absence of `video.w` and `video.h`, this field is required. | video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. |video.minduration| Required | Integer | Minimum video ad duration in seconds. |video.maxduration| Required | Integer | Maximum video ad duration in seconds. |video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper +## Deprecation warning + +We are deprecating the older format +of having `mediaType` and `sizes` at the ad unit level. + +Here are examples of the format. + +##### Older Deprecated Format +```javascript +var adUnits = [{ + // ... + + sizes: [ + [300, 250], + [300, 600] + ] + + // ... +}]; +``` + Setup Guide =========== @@ -100,10 +106,15 @@ Setup Guide Follow these steps to configure and add the IX module to your Prebid.js integration. +Both video and banner params will be read from the `adUnits[].mediaTypes.video` and `adUnits[].mediaTypes.banner` respectively. + The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply). + In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit. + + ```javascript var adUnits = [{ code: 'banner-div-a', @@ -134,21 +145,23 @@ var adUnits = [{ ### 1. Add IX to the appropriate ad units -For each size in an ad unit that IX will be bidding on, add one of the following +For each ad unit that IX will be bidding on, add one of the following bid objects under `adUnits[].bids`: +- size is optional and deprecated ```javascript { bidder: 'ix', params: { siteId: '', - size: [] + size: [] // deprecated } } ``` -Set `params.siteId` and `params.size` in each bid object to the values provided +Set `params.siteId` in the bid object to the values provided by your IX representative. +- `params.size` is not required anymore **Examples** @@ -167,14 +180,7 @@ var adUnits = [{ bids: [{ bidder: 'ix', params: { - siteId: '12345', - size: [300, 250] - } - }, { - bidder: 'ix', - params: { - siteId: '12345', - size: [300, 600] + siteId: '12345' } }] }]; @@ -185,35 +191,30 @@ var adUnits = [{ code: 'video-div-a', mediaTypes: { video: { + // Preferred location for openrtb v2.5 compatible video obj context: 'instream', - playerSize: [ - [300, 250], - [300, 600] - ] + playerSize: [300, 250], + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] } }, bids: [{ bidder: 'ix', params: { - siteId: '12345', - size: [300, 250], - video: { - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] - } + siteId: '12345' } }, { bidder: 'ix', params: { siteId: '12345', - size: [300, 600], video: { // openrtb v2.5 compatible video obj + // If required, use this to override mediaTypes.video.XX properties } } }] @@ -231,7 +232,14 @@ var adUnits = [{ mediaTypes: { video: { context: 'outstream', - playerSize: [[300, 250]] + playerSize: [300, 250], + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] } }, renderer: { @@ -244,15 +252,8 @@ var adUnits = [{ bidder: 'ix', params: { siteId: '12345', - size: [300, 250], video: { - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] + // If required, use this to override mediaTypes.video.XX properties } } }] @@ -288,6 +289,26 @@ pbjs.setConfig({ } }); ``` +#### The **detectMissingSizes** feature +By default, the IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. If you want the IX bidding adapter to only bid on the banner size it’s configured to, switch off this feature using `detectMissingSizes`. +``` +pbjs.setConfig({ + ix: { + detectMissingSizes: false + } + }); +``` +OR +``` +pbjs.setBidderConfig({ + bidders: ["ix"], + config: { + ix: { + detectMissingSizes: false + } + } + }); +``` ### 2. Include `ixBidAdapter` in your build process @@ -393,11 +414,9 @@ FAQs ### Why do I have to input size in `adUnits[].bids[].params` for IX when the size is already in the ad unit? -There is one important reason why we recommend it: +No, only `siteId` is required. -1. An IX site ID is recommended to map to a single size, whereas an ad unit can have multiple -sizes. To ensure that the right site ID is mapped to the correct size in the ad unit, -we require the size to be explicitly stated or our bidder will auto assign the site ID to sizes that are not assigned. +The `size` parameter is no longer a required field, the `siteId` will now be associated with all the sizes in the ad unit. ### How do I view IX's bid request in the network? diff --git a/modules/jcmBidAdapter.js b/modules/jcmBidAdapter.js deleted file mode 100644 index c8d2f8bdd52..00000000000 --- a/modules/jcmBidAdapter.js +++ /dev/null @@ -1,89 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'jcm'; -const URL = 'https://media.adfrontiers.com/pq' - -export const spec = { - code: BIDDER_CODE, - aliases: ['jcarter'], - isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.siteId && bid.bidId); - }, - - buildRequests: function(validBidRequests) { - var BidRequestStr = { - bids: [] - }; - - for (var i = 0; i < validBidRequests.length; i++) { - var adSizes = ''; - var bid = validBidRequests[i]; - for (var x = 0; x < bid.sizes.length; x++) { - adSizes += utils.parseGPTSingleSizeArray(bid.sizes[x]); - if (x !== (bid.sizes.length - 1)) { - adSizes += ','; - } - } - - BidRequestStr.bids.push({ - 'callbackId': bid.bidId, - 'siteId': bid.params.siteId, - 'adSizes': adSizes, - }); - } - - var JSONStr = JSON.stringify(BidRequestStr); - var dataStr = 't=hb&ver=1.0&compact=true&bids=' + encodeURIComponent(JSONStr); - - return { - method: 'GET', - url: URL, - data: dataStr - } - }, - - interpretResponse: function(serverResponse) { - const bidResponses = []; - serverResponse = serverResponse.body; - // loop through serverResponses - if (serverResponse) { - if (serverResponse.bids) { - var bids = serverResponse.bids; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - const bidResponse = { - requestId: bid.callbackId, - bidderCode: spec.code, - cpm: bid.cpm, - width: bid.width, - height: bid.height, - creativeId: bid.creativeId, - currency: 'USD', - netRevenue: bid.netRevenue, - ttl: bid.ttl, - ad: decodeURIComponent(bid.ad.replace(/\+/g, '%20')) - }; - bidResponses.push(bidResponse); - }; - }; - } - return bidResponses; - }, - - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://media.adfrontiers.com/hb/jcm_usersync.html' - }]; - } - if (syncOptions.image) { - return [{ - type: 'image', - url: 'https://media.adfrontiers.com/hb/jcm_usersync.png' - }]; - } - } -} - -registerBidder(spec); diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js new file mode 100644 index 00000000000..db6c9032e65 --- /dev/null +++ b/modules/jixieBidAdapter.js @@ -0,0 +1,264 @@ +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'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { Renderer } from '../src/Renderer.js'; +export const storage = getStorageManager(); + +const BIDDER_CODE = 'jixie'; +const EVENTS_URL = 'https://hbtra.jixie.io/sync/hb?'; +const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.io/jxhboutstream.js'; +const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost'; +const sidTTLMins_ = 30; + +/** + * Own miscellaneous support functions: + */ + +function setIds_(clientId, sessionId) { + let dd = null; + try { + dd = window.location.hostname.match(/[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?$/mg); + } catch (err1) {} + try { + let expC = (new Date(new Date().setFullYear(new Date().getFullYear() + 1))).toUTCString(); + let expS = (new Date(new Date().setMinutes(new Date().getMinutes() + sidTTLMins_))).toUTCString(); + + storage.setCookie('_jx', clientId, expC, 'None', null); + storage.setCookie('_jx', clientId, expC, 'None', dd); + + storage.setCookie('_jxs', sessionId, expS, 'None', null); + storage.setCookie('_jxs', sessionId, expS, 'None', dd); + + storage.setDataInLocalStorage('_jx', clientId); + storage.setDataInLocalStorage('_jxs', sessionId); + } catch (error) {} +} + +function fetchIds_() { + let ret = { + client_id_c: '', + client_id_ls: '', + session_id_c: '', + session_id_ls: '' + }; + try { + let tmp = storage.getCookie('_jx'); + if (tmp) ret.client_id_c = tmp; + tmp = storage.getCookie('_jxs'); + if (tmp) ret.session_id_c = tmp; + + tmp = storage.getDataFromLocalStorage('_jx'); + if (tmp) ret.client_id_ls = tmp; + tmp = storage.getDataFromLocalStorage('_jxs'); + if (tmp) ret.session_id_ls = tmp; + } catch (error) {} + return ret; +} + +function getDevice_() { + return ((/(ios|ipod|ipad|iphone|android|blackberry|iemobile|opera mini|webos)/i).test(navigator.userAgent) + ? 'mobile' : 'desktop'); +} + +function pingTracking_(endpointOverride, qpobj) { + internal.ajax((endpointOverride || EVENTS_URL), null, qpobj, { + withCredentials: true, + method: 'GET', + crossOrigin: true + }); +} + +function jxOutstreamRender_(bidAd) { + bidAd.renderer.push(() => { + window.JixieOutstreamVideo.init({ + sizes: [bidAd.width, bidAd.height], + width: bidAd.width, + height: bidAd.height, + targetId: bidAd.adUnitCode, + adResponse: bidAd.adResponse + }); + }); +} + +function createRenderer_(bidAd, scriptUrl, createFcn) { + const renderer = Renderer.install({ + id: bidAd.adUnitCode, + url: scriptUrl, + loaded: false, + config: {'player_height': bidAd.height, 'player_width': bidAd.width}, + adUnitCode: bidAd.adUnitCode + }); + try { + renderer.setRender(createFcn); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function getMiscDims_() { + let ret = { + pageurl: '', + domain: '', + device: 'unknown' + } + try { + let refererInfo_ = getRefererInfo(); + let url_ = ((refererInfo_ && refererInfo_.referer) ? refererInfo_.referer : window.location.href); + ret.pageurl = url_; + ret.domain = utils.parseUrl(url_).host; + ret.device = getDevice_(); + } catch (error) {} + return ret; +} + +// easier for replacement in the unit test +export const internal = { + getDevice: getDevice_, + getRefererInfo: getRefererInfo, + ajax: ajax, + getMiscDims: getMiscDims_ +}; + +export const spec = { + code: BIDDER_CODE, + EVENTS_URL: EVENTS_URL, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + if (typeof bid.params.unit === 'undefined') { + return false; + } + return true; + }, + buildRequests: function(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig('currency'); + const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + + let bids = []; + validBidRequests.forEach(function(one) { + bids.push({ + bidId: one.bidId, + adUnitCode: one.adUnitCode, + mediaTypes: (one.mediaTypes === 'undefined' ? {} : one.mediaTypes), + sizes: (one.sizes === 'undefined' ? [] : one.sizes), + params: one.params, + }); + }); + + let jixieCfgBlob = config.getConfig('jixie'); + if (!jixieCfgBlob) { + jixieCfgBlob = {}; + } + + let ids = fetchIds_(); + let miscDims = internal.getMiscDims(); + let transformedParams = Object.assign({}, { + auctionid: bidderRequest.auctionId, + timeout: bidderRequest.timeout, + currency: currency, + timestamp: (new Date()).getTime(), + device: miscDims.device, + domain: miscDims.domain, + pageurl: miscDims.pageurl, + bids: bids, + cfg: jixieCfgBlob + }, ids); + return Object.assign({}, { + method: 'POST', + url: REQUESTS_URL, + data: JSON.stringify(transformedParams), + currency: currency + }); + }, + + onTimeout: function(timeoutData) { + let jxCfgBlob = config.getConfig('jixie'); + if (jxCfgBlob && jxCfgBlob.onTimeout == 'off') { + return; + } + let url = null;// default + if (jxCfgBlob && jxCfgBlob.onTimeoutUrl && typeof jxCfgBlob.onTimeoutUrl == 'string') { + url = jxCfgBlob.onTimeoutUrl; + } + let miscDims = internal.getMiscDims(); + pingTracking_(url, // no overriding ping URL . just use default + { + action: 'hbtimeout', + device: miscDims.device, + pageurl: encodeURIComponent(miscDims.pageurl), + domain: encodeURIComponent(miscDims.domain), + auctionid: utils.deepAccess(timeoutData, '0.auctionId'), + timeout: utils.deepAccess(timeoutData, '0.timeout'), + count: timeoutData.length + }); + }, + + onBidWon: function(bid) { + if (bid.notrack) { + return; + } + if (bid.trackingUrl) { + pingTracking_(bid.trackingUrl, {}); + } else { + let miscDims = internal.getMiscDims(); + pingTracking_((bid.trackingUrlBase ? bid.trackingUrlBase : null), { + action: 'hbbidwon', + device: miscDims.device, + pageurl: encodeURIComponent(miscDims.pageurl), + domain: encodeURIComponent(miscDims.domain), + cid: bid.cid, + cpid: bid.cpid, + jxbidid: bid.jxBidId, + auctionid: bid.auctionId, + cpm: bid.cpm, + requestid: bid.requestId + }); + } + }, + + interpretResponse: function(response, bidRequest) { + if (response && response.body && utils.isArray(response.body.bids)) { + const bidResponses = []; + response.body.bids.forEach(function(oneBid) { + let bnd = {}; + + Object.assign(bnd, oneBid); + if (oneBid.osplayer) { + bnd.adResponse = { + content: oneBid.vastXml, + parameters: oneBid.osparams, + height: oneBid.height, + width: oneBid.width + }; + let rendererScript = (oneBid.osparams.script ? oneBid.osparams.script : JX_OUTSTREAM_RENDERER_URL); + bnd.renderer = createRenderer_(oneBid, rendererScript, jxOutstreamRender_); + } + // a note on advertiserDomains: our adserver is not responding in + // openRTB-type json. so there is no need to copy from 'adomain' over + // to meta: advertiserDomains + // However, we will just make sure the property is there. + if (!bnd.meta) { + bnd.meta = {}; + } + if (!bnd.meta.advertiserDomains) { + bnd.meta.advertiserDomains = []; + } + bidResponses.push(bnd); + }); + if (response.body.setids) { + setIds_(response.body.setids.client_id, + response.body.setids.session_id); + } + return bidResponses; + } else { return []; } + } +} + +registerBidder(spec); diff --git a/modules/jixieBidAdapter.md b/modules/jixieBidAdapter.md new file mode 100644 index 00000000000..d9c1f19541d --- /dev/null +++ b/modules/jixieBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +Module Name: Jixie Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@jixie.io + +# Description + +Module that connects to Jixie demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'demoslot1-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [ + { + bidder: 'jixie', + params: { + unit: "prebidsampleunit" + } + } + ] + }, + { + code: 'demoslot2-div', + sizes: [640, 360], + mediaTypes: { + video: { + playerSize: [ + [640, 360] + ], + context: "outstream" + } + }, + bids: [ + { + bidder: 'jixie', + params: { + unit: "prebidsampleunit" + } + } + ] + }, + { + code: 'demoslot3-div', + sizes: [640, 360], + mediaTypes: { + video: { + playerSize: [ + [640, 360] + ], + context: "instream" + } + }, + bids: [ + { + bidder: 'jixie', + params: { + unit: "prebidsampleunit" + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 1f84feee7d1..da30ee16427 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -2,12 +2,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { deepAccess } from '../src/utils.js'; const BIDDER_CODE = 'justpremium' +const GVLID = 62 const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr' const JP_ADAPTER_VERSION = '1.7' const pixels = [] export const spec = { code: BIDDER_CODE, + gvlid: GVLID, time: 60000, isBidRequestValid: (bid) => { @@ -90,7 +92,10 @@ export const spec = { netRevenue: true, currency: bid.currency || 'USD', ttl: bid.ttl || spec.time, - format: bid.format + format: bid.format, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length > 0 ? bid.adomain : [] + } } bidResponses.push(bidResponse) } diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js new file mode 100644 index 00000000000..8332e720ae7 --- /dev/null +++ b/modules/jwplayerRtdProvider.js @@ -0,0 +1,282 @@ +/** + * This module adds the jwplayer provider to the Real Time Data module (rtdModule) + * The {@link module:modules/realTimeData} module is required + * The module will allow Ad Bidders to obtain JW Player's Video Ad Targeting information + * The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid + * requests are made while the segments are being fetched, they will be blocked until all requests complete, or the + * timeout expires. + * @module modules/jwplayerRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { config } from '../src/config.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { logError } from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const SUBMODULE_NAME = 'jwplayer'; +const segCache = {}; +const pendingRequests = {}; +let activeRequestCount = 0; +let resumeBidRequest; + +/** @type {RtdSubmodule} */ +export const jwplayerSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + /** + * add targeting data to bids and signal completion to realTimeData module + * @function + * @param {Obj} bidReqConfig + * @param {function} onDone + */ + getBidRequestData: enrichBidRequest, + init +}; + +config.getConfig('realTimeData', ({realTimeData}) => { + const providers = realTimeData.dataProviders; + const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME); + const params = jwplayerProvider && jwplayerProvider.params; + if (!params) { + return; + } + fetchTargetingInformation(params); +}); + +submodule('realTimeData', jwplayerSubmodule); + +function init(provider, userConsent) { + return true; +} + +export function fetchTargetingInformation(jwTargeting) { + const mediaIDs = jwTargeting.mediaIDs; + if (!mediaIDs) { + return; + } + mediaIDs.forEach(mediaID => { + fetchTargetingForMediaId(mediaID); + }); +} + +export function fetchTargetingForMediaId(mediaId) { + const ajax = ajaxBuilder(); + // TODO: Avoid checking undefined vs null by setting a callback to pendingRequests. + pendingRequests[mediaId] = null; + ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { + success: function (response) { + const segment = parseSegment(response); + cacheSegments(segment, mediaId); + onRequestCompleted(mediaId, !!segment); + }, + error: function () { + logError('failed to retrieve targeting information'); + onRequestCompleted(mediaId, false); + } + }); +} + +function parseSegment(response) { + let segment; + try { + const data = JSON.parse(response); + if (!data) { + throw ('Empty response'); + } + + const playlist = data.playlist; + if (!playlist || !playlist.length) { + throw ('Empty playlist'); + } + + segment = playlist[0].jwpseg; + } catch (err) { + logError(err); + } + return segment; +} + +function cacheSegments(jwpseg, mediaId) { + if (jwpseg && mediaId) { + segCache[mediaId] = jwpseg; + } +} + +function onRequestCompleted(mediaID, success) { + const callback = pendingRequests[mediaID]; + if (callback) { + callback(success ? getVatFromCache(mediaID) : { mediaID }); + activeRequestCount--; + } + delete pendingRequests[mediaID]; + + if (activeRequestCount > 0) { + return; + } + + if (resumeBidRequest) { + resumeBidRequest(); + resumeBidRequest = null; + } +} + +function enrichBidRequest(bidReqConfig, onDone) { + activeRequestCount = 0; + const adUnits = bidReqConfig.adUnits || getGlobal().adUnits; + enrichAdUnits(adUnits); + if (activeRequestCount <= 0) { + onDone(); + } else { + resumeBidRequest = onDone; + } +} + +/** + * get targeting data and write to bids + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ +export function enrichAdUnits(adUnits) { + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); + adUnits.forEach(adUnit => { + const jwTargeting = extractPublisherParams(adUnit, fpdFallback); + if (!jwTargeting || !Object.keys(jwTargeting).length) { + return; + } + + const onVatResponse = function (vat) { + if (!vat) { + return; + } + const targeting = formatTargetingResponse(vat); + addTargetingToBids(adUnit.bids, targeting); + }; + loadVat(jwTargeting, onVatResponse); + }); +} + +function supportsInstreamVideo(mediaTypes) { + const video = mediaTypes && mediaTypes.video; + return video && video.context === 'instream'; +} + +export function extractPublisherParams(adUnit, fallback) { + let adUnitTargeting; + try { + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; + } catch (e) {} + + if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { + return; + } + + return Object.assign({}, fallback, adUnitTargeting); +} + +function loadVat(params, onCompletion) { + const { playerID, mediaID } = params; + if (pendingRequests[mediaID] !== undefined) { + loadVatForPendingRequest(playerID, mediaID, onCompletion); + return; + } + + const vat = getVatFromCache(mediaID) || getVatFromPlayer(playerID, mediaID) || { mediaID }; + onCompletion(vat); +} + +function loadVatForPendingRequest(playerID, mediaID, callback) { + const vat = getVatFromPlayer(playerID, mediaID); + if (vat) { + callback(vat); + } else { + activeRequestCount++; + pendingRequests[mediaID] = callback; + } +} + +export function getVatFromCache(mediaID) { + const segments = segCache[mediaID]; + + if (!segments) { + return null; + } + + return { + segments, + mediaID + }; +} + +export function getVatFromPlayer(playerID, mediaID) { + const player = getPlayer(playerID); + if (!player) { + return null; + } + + const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); + if (!item) { + return null; + } + + mediaID = mediaID || item.mediaid; + const segments = item.jwpseg; + cacheSegments(segments, mediaID) + + return { + segments, + mediaID + }; +} + +export function formatTargetingResponse(vat) { + const { segments, mediaID } = vat; + const targeting = {}; + if (segments && segments.length) { + targeting.segments = segments; + } + + if (mediaID) { + const id = 'jw_' + mediaID; + targeting.content = { + id + } + } + return targeting; +} + +function addTargetingToBids(bids, targeting) { + if (!bids || !targeting) { + return; + } + + bids.forEach(bid => addTargetingToBid(bid, targeting)); +} + +export function addTargetingToBid(bid, targeting) { + const rtd = bid.rtd || {}; + const jwRtd = {}; + jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting }); + bid.rtd = Object.assign({}, rtd, jwRtd); +} + +function getPlayer(playerID) { + const jwplayer = window.jwplayer; + if (!jwplayer) { + logError('jwplayer.js was not found on page'); + return; + } + + const player = jwplayer(playerID); + if (!player || !player.getPlaylist) { + logError('player ID did not match any players'); + return; + } + return player; +} diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md new file mode 100644 index 00000000000..7fb1bb13d74 --- /dev/null +++ b/modules/jwplayerRtdProvider.md @@ -0,0 +1,125 @@ +The purpose of this Real Time Data Provider is to allow publishers to target against their JW Player media without +having to integrate with the Player Bidding product. This prebid module makes JW Player's video ad targeting information accessible +to Bid Adapters. + +# Usage for Publishers: + +Compile the JW Player RTD Provider into your Prebid build: + +`gulp build --modules=jwplayerRtdProvider` + +Publishers must register JW Player as a real time data provider by setting up a Prebid Config conformant to the +following structure: + +```javascript +const jwplayerDataProvider = { + name: "jwplayer" +}; + +pbjs.setConfig({ + ..., + realTimeData: { + dataProviders: [ + jwplayerDataProvider + ] + } +}); +``` +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: + +```javascript +const adUnit = { + code: '/19968336/prebid_native_example_1', + ..., + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: 'abcd', + mediaID: '1234' + } + } + } + } +}; + +pbjs.que.push(function() { + pbjs.addAdUnits([adUnit]); + pbjs.requestBids({ + ... + }); +}); +``` +**Note**: The player ID is the ID of the HTML div element used when instantiating the player. +You can retrieve this ID by calling `player.id`, where player is the JW Player instance variable. + +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. + +## Prefetching +In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: + +```javascript +const jwplayerDataProvider = { + name: "jwplayer", + waitForIt: true, + params: { + mediaIDs: ['abc', 'def', 'ghi', 'jkl'] + } +}; +``` + +You must also set a value to `auctionDelay` in the config's `realTimeData` object + +```javascript +realTimeData = { + auctionDelay: 100, + ... +}; +``` + +# Usage for Bid Adapters: + +Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. +Each bid for which targeting information was found will conform to the following object structure: + +```javascript +{ + adUnitCode: 'xyz', + bidId: 'abc', + ..., + rtd: { + jwplayer: { + targeting: { + segments: ['123', '456'], + content: { + id: 'jw_abc123' + } + } + } + } +} +``` + +where: +- `segments` is an array of jwpseg targeting segments, of type string. +- `content` is an object containing metadata for the media. It may contain the following information: + - `id` is a unique identifier for the specific media asset. + +**Example:** + +To view an example: + +- in your cli run: + +`gulp serve --modules=jwplayerRtdProvider` + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` + +**Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +# Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 31c35f4afe3..610f4558139 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -3,17 +3,19 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}'; +const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; const SYNC_COUNT = 5; +const GVLID = 972; +const storage = getStorageManager(GVLID, BIDDER_CODE); let sessionId, lastPageUrl, requestCounter; export const spec = { + gvlid: GVLID, code: BIDDER_CODE, isBidRequestValid: function(bid) { if (!bid || !bid.params) { @@ -48,7 +50,7 @@ export const spec = { bidIDs: bidIds, bidSizes: bidSizes, prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(tdid, bidderRequest.uspConsent)); + }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); return Object.assign({}, bidderRequest, { method: 'GET', @@ -65,7 +67,8 @@ export const spec = { let meta; if (adUnit.metadata && adUnit.metadata.landingPageDomain) { meta = { - clickUrl: adUnit.metadata.landingPageDomain + clickUrl: adUnit.metadata.landingPageDomain, + advertiserDomains: [adUnit.metadata.landingPageDomain] }; } bidResponses.push({ @@ -84,15 +87,25 @@ export const spec = { } return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { const syncs = []; const seed = spec._generateRandomUuid(); const clientId = spec._getClientId(); + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { + return syncs; + } if (syncOptions.iframeEnabled && seed && clientId) { for (let i = 0; i < SYNC_COUNT; i++) { syncs.push({ type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed).replace('{INDEX}', i) + url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') }); } } @@ -182,7 +195,7 @@ export const spec = { } }, - _getUserIds(tdid, usp) { + _getUserIds(tdid, usp, gdpr) { const crb = spec._getCrb(); const userIds = { kargoID: crb.userId, @@ -191,6 +204,16 @@ export const spec = { optOut: crb.optOut, usp: usp }; + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } + } + } catch (e) { + } if (tdid) { userIds.tdID = tdid; } @@ -202,9 +225,9 @@ export const spec = { return crb.clientId; }, - _getAllMetadata(tdid, usp) { + _getAllMetadata(tdid, usp, gdpr) { return { - userIDs: spec._getUserIds(tdid, usp), + userIDs: spec._getUserIds(tdid, usp, gdpr), krux: spec._getKrux(), pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), diff --git a/modules/kinessoIdSystem.js b/modules/kinessoIdSystem.js new file mode 100644 index 00000000000..2cd7f594dd2 --- /dev/null +++ b/modules/kinessoIdSystem.js @@ -0,0 +1,241 @@ +/** + * This module adds KinessoId ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/KinessoIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {coppaDataHandler, uspDataHandler} from '../src/adapterManager.js'; + +const MODULE_NAME = 'kpuid'; +const ID_SVC = 'https://id.knsso.com/id'; +// 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(); + +/** + * 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); + }; +} + +/** + * 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 random character + * @param len + * @param prng + * @returns {string} + */ +function encodeRandom(len, prng) { + let str = ''; + for (; len > 0; len--) { + str = randomChar(prng) + str; + } + return str; +} + +/** + * 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'); + } + + 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; +} + +/** + * 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 = 'kinessoId'; + return err; +} + +/** + * 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(); +} + +/** + * existing id generation call back + * @param result + * @param callback + * @returns {{success: success, error: error}} + */ +function syncId(storedId) { + return { + success: function (responseBody) { + utils.logInfo('KinessoId: id to be synced: ' + storedId); + }, + error: function () { + utils.logInfo('KinessoId: Sync error for id : ' + storedId); + } + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + const knssoId = (value && typeof value === 'string') ? value : undefined; + if (knssoId) { + result.kpuid = knssoId; + utils.logInfo('KinessoId: Decoded value ' + JSON.stringify(result)); + return result; + } + return knssoId; +} + +/** + * Builds and returns the shared Id URL with attached consent data if applicable + * @param {Object} consentData + * @return {string} + */ +function kinessoSyncUrl(accountId, consentData) { + const usPrivacyString = uspDataHandler.getConsentData(); + let kinessoSyncUrl = `${ID_SVC}?accountid=${accountId}`; + if (usPrivacyString) { + kinessoSyncUrl = `${kinessoSyncUrl}?us_privacy=${usPrivacyString}`; + } + if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return kinessoSyncUrl; + + kinessoSyncUrl = `${kinessoSyncUrl}&gdpr=1&gdpr_consent=${consentData.consentString}`; + return kinessoSyncUrl +} + +/** @type {Submodule} */ +export const kinessoIdSubmodule = { + + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{kpuid:{ id: string}} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData + * @returns {knssoId} + */ + getId(config, consentData) { + const configParams = (config && config.params) || {}; + if (!configParams || typeof configParams.accountid !== 'number') { + utils.logError('User ID - KinessoId submodule requires a valid accountid to be defined'); + return; + } + const coppa = coppaDataHandler.getCoppa(); + if (coppa) { + utils.logInfo('KinessoId: IDs not provided for coppa requests, exiting KinessoId'); + return; + } + const accountId = configParams.accountid; + const knnsoId = id(); + utils.logInfo('KinessoId: generated id ' + knnsoId); + const kinessoIdPayload = {}; + kinessoIdPayload.id = knnsoId; + const payloadString = JSON.stringify(kinessoIdPayload); + ajax(kinessoSyncUrl(accountId, consentData), syncId(knnsoId), payloadString, {method: 'POST', withCredentials: true}); + return {'id': knnsoId}; + } + +}; + +// Register submodule for userId +submodule('userId', kinessoIdSubmodule); diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js new file mode 100644 index 00000000000..59d1639a329 --- /dev/null +++ b/modules/koblerBidAdapter.js @@ -0,0 +1,238 @@ +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'; +import {getRefererInfo} from '../src/refererDetection.js'; + +const BIDDER_CODE = 'kobler'; +const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; +const SUPPORTED_CURRENCY = 'USD'; +const DEFAULT_TIMEOUT = 1000; +const TIME_TO_LIVE_IN_SECONDS = 10 * 60; + +export const isBidRequestValid = function (bid) { + return !!(bid && bid.bidId && bid.params && bid.params.placementId); +}; + +export const buildRequests = function (validBidRequests, bidderRequest) { + return { + method: 'POST', + url: BIDDER_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), + options: { + contentType: 'application/json' + } + }; +}; + +export const interpretResponse = function (serverResponse) { + const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; + const res = serverResponse.body; + const bids = [] + if (res) { + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + const adWithCorrectCurrency = b.adm + .replace(/\${AUCTION_PRICE_CURRENCY}/g, adServerPriceCurrency); + bids.push({ + requestId: b.impid, + cpm: b.price, + currency: res.cur, + width: b.w, + height: b.h, + creativeId: b.crid, + dealId: b.dealid, + netRevenue: true, + ttl: TIME_TO_LIVE_IN_SECONDS, + ad: adWithCorrectCurrency, + nurl: b.nurl, + meta: { + advertiserDomains: b.adomain + } + }) + }) + }); + } + return bids; +}; + +export const onBidWon = function (bid) { + const cpm = bid.cpm || 0; + const cpmCurrency = bid.currency || SUPPORTED_CURRENCY; + const adServerPrice = utils.deepAccess(bid, 'adserverTargeting.hb_pb', 0); + const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; + if (utils.isStr(bid.nurl) && bid.nurl !== '') { + const winNotificationUrl = utils.replaceAuctionPrice(bid.nurl, cpm) + .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) + .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) + .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); + utils.triggerPixel(winNotificationUrl); + } +}; + +export const onTimeout = function (timeoutDataArray) { + if (utils.isArray(timeoutDataArray)) { + const refererInfo = getRefererInfo(); + const pageUrl = (refererInfo && refererInfo.referer) + ? refererInfo.referer + : window.location.href; + timeoutDataArray.forEach(timeoutData => { + const query = utils.parseQueryStringParameters({ + ad_unit_code: timeoutData.adUnitCode, + auction_id: timeoutData.auctionId, + bid_id: timeoutData.bidId, + timeout: timeoutData.timeout, + placement_id: utils.deepAccess(timeoutData, 'params.0.placementId'), + page_url: pageUrl, + }); + const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; + utils.triggerPixel(timeoutNotificationUrl); + }); + } +}; + +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + const imps = validBidRequests.map(buildOpenRtbImpObject); + const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) + ? bidderRequest.refererInfo.referer + : window.location.href; + + const request = { + id: bidderRequest.auctionId, + at: 1, + tmax: timeout, + cur: [SUPPORTED_CURRENCY], + imp: imps, + device: { + devicetype: getDevice(), + geo: getGeo(validBidRequests[0]) + }, + site: { + page: pageUrl, + }, + test: getTest(validBidRequests[0]) + }; + + return JSON.stringify(request); +} + +function buildOpenRtbImpObject(validBidRequest) { + const sizes = getSizes(validBidRequest); + const mainSize = sizes[0]; + const floorInfo = getFloorInfo(validBidRequest, mainSize); + + return { + id: validBidRequest.bidId, + banner: { + format: buildFormatArray(sizes), + w: mainSize[0], + h: mainSize[1], + ext: { + kobler: { + pos: getPosition(validBidRequest) + } + } + }, + tagid: validBidRequest.params.placementId, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, + pmp: buildPmpObject(validBidRequest) + }; +} + +function getDevice() { + const ws = utils.getWindowSelf(); + const ua = ws.navigator.userAgent; + + if (/(tablet|ipad|playbook|silk|android 3.0|xoom|sch-i800|kindle)|(android(?!.*mobi))/i + .test(ua.toLowerCase())) { + return 5; // tablet + } + 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([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i + .test(ua.toLowerCase())) { + return 4; // phone + } + return 2; // personal computers +} + +function getGeo(validBidRequest) { + if (validBidRequest.params.zip) { + return { + zip: validBidRequest.params.zip + }; + } + return {}; +} + +function getTest(validBidRequest) { + return validBidRequest.params.test ? 1 : 0; +} + +function getSizes(validBidRequest) { + const sizes = utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes', validBidRequest.sizes); + if (utils.isArray(sizes) && sizes.length > 0) { + return sizes; + } + + return [[0, 0]]; +} + +function buildFormatArray(sizes) { + return sizes.map(size => { + return { + w: size[0], + h: size[1] + }; + }); +} + +function getPosition(validBidRequest) { + return parseInt(validBidRequest.params.position) || 0; +} + +function getFloorInfo(validBidRequest, mainSize) { + if (typeof validBidRequest.getFloor === 'function') { + const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; + return validBidRequest.getFloor({ + currency: SUPPORTED_CURRENCY, + mediaType: BANNER, + size: sizeParam + }); + } else { + return { + currency: SUPPORTED_CURRENCY, + floor: getFloorPrice(validBidRequest) + }; + } +} + +function getFloorPrice(validBidRequest) { + return parseFloat(validBidRequest.params.floorPrice) || 0.0; +} + +function buildPmpObject(validBidRequest) { + if (validBidRequest.params.dealIds) { + return { + deals: validBidRequest.params.dealIds.map(dealId => { + return { + id: dealId + }; + }) + }; + } + return {}; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + onTimeout +}; + +registerBidder(spec); diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md new file mode 100644 index 00000000000..7a7b2388367 --- /dev/null +++ b/modules/koblerBidAdapter.md @@ -0,0 +1,67 @@ +# Overview + +**Module Name**: Kobler Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: bidding-support@kobler.no + +# Description + +Connects to Kobler's demand sources. + +This adapter currently only supports Banner Ads. + +# Parameters + +| Parameter (in `params`) | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | +| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +# Test Parameters +```javascript + const adUnits = [{ + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[320, 250], [300, 250]], + } + }, + bids: [{ + bidder: 'kobler', + params: { + placementId: 'k5H7et3R0' + } + }] + }]; +``` + +In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: +- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. +- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. + +# Test Optional Parameters +```javascript + const adUnits = [{ + code: 'div-gpt-ad-1460505748561-1', + mediaTypes: { + banner: { + sizes: [[320, 250], [300, 250]], + } + }, + bids: [{ + bidder: 'kobler', + params: { + placementId: 'k5H7et3R0', + zip: '102 22', + test: true, + floorPrice: 5.0, + position: 1, + dealIds: ['abc328745', 'mxw243253'] + } + }] + }]; +``` diff --git a/modules/komoonaBidAdapter.js b/modules/komoonaBidAdapter.js deleted file mode 100644 index 6adc0eb1984..00000000000 --- a/modules/komoonaBidAdapter.js +++ /dev/null @@ -1,121 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'komoona'; -const ENDPOINT = 'https://bidder.komoona.com/v1/GetSBids'; -const USYNCURL = 'https://s.komoona.com/sync/usync.html'; - -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 {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; - }); - - // Komoona 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: kbConf }; - } - - return { - method: 'POST', - url: ENDPOINT, - 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, request) => { - const bidResponses = []; - try { - if (response.body && response.body.bids) { - response.body.bids.forEach(bid => { - // The bid ID. Used to tie this bid back to the request. - bid.requestId = bid.uuid; - // The creative payload of the returned bid. - bid.ad = bid.creative; - bidResponses.push(bid); - }); - } - } catch (error) { - utils.logError(error); - } - 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/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js new file mode 100644 index 00000000000..db024230e2a --- /dev/null +++ b/modules/krushmediaBidAdapter.js @@ -0,0 +1,158 @@ +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 = 'krushmedia'; +const AD_URL = 'https://ads4.krushmedia.com/?c=rtb&m=hb'; +const SYNC_URL = 'https://cs.krushmedia.com/html?src=pbjs' + +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; + } +} + +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.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.key))); + }, + + 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); + }; + + 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 = { + key: bid.params.key, + bidId: bid.bidId, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {}, + bidFloor: getBidFloor(bid) + }; + + if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + placement.sizes = bid.mediaTypes[BANNER].sizes; + } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; + placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; + 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; + } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { + placement.native = bid.mediaTypes[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)) { + 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 syncUrl = SYNC_URL + 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: 'iframe', + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/krushmediaBidAdapter.md b/modules/krushmediaBidAdapter.md new file mode 100644 index 00000000000..7bf7c4fe491 --- /dev/null +++ b/modules/krushmediaBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: krushmedia Bidder Adapter +Module Type: krushmedia Bidder Adapter +Maintainer: adapter@krushmedia.com +``` + +# Description + +Module that connects to krushmedia demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'video' + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'native' + } + } + ] + } + ]; +``` diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js new file mode 100644 index 00000000000..6751a8a567c --- /dev/null +++ b/modules/kubientBidAdapter.js @@ -0,0 +1,152 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'kubient'; +const END_POINT = 'https://kssp.kbntx.ch/kubprebidjs'; +const VERSION = '1.1'; +const VENDOR_ID = 794; +export const spec = { + code: BIDDER_CODE, + gvlid: VENDOR_ID, + supportedMediaTypes: [ BANNER, VIDEO ], + isBidRequestValid: function (bid) { + return !!( + bid && + bid.params && + bid.params.zoneid && + ((!bid.mediaTypes.video) || (bid.mediaTypes.video && bid.mediaTypes.video.playerSize && bid.mediaTypes.video.mimes && bid.mediaTypes.video.protocols)) + ); + }, + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + return validBidRequests.map(function (bid) { + let floor = 0.0; + if (typeof bid.getFloor === 'function') { + const mediaType = (Object.keys(bid.mediaTypes).length == 1) ? Object.keys(bid.mediaTypes)[0] : '*'; + const sizes = bid.sizes || '*'; + const floorInfo = bid.getFloor({currency: 'USD', mediaType: mediaType, size: sizes}); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } + + let adSlot = { + bidId: bid.bidId, + zoneId: bid.params.zoneid || '', + floor: floor || 0.0 + }; + + if (bid.mediaTypes.banner) { + adSlot.banner = bid.mediaTypes.banner; + } + + if (bid.mediaTypes.video) { + adSlot.video = bid.mediaTypes.video; + } + + if (bid.schain) { + adSlot.schain = bid.schain; + } + + let data = { + v: VERSION, + requestId: bid.bidderRequestId, + adSlots: [adSlot], + tmax: bidderRequest.timeout, + gdpr: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0, + consentGiven: kubientGetConsentGiven(bidderRequest.gdprConsent), + uspConsent: bidderRequest.uspConsent + }; + + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + data.referer = bidderRequest.refererInfo.referer + } + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { + data.consent = bidderRequest.gdprConsent.consentString + } + + return { + method: 'POST', + url: END_POINT, + data: JSON.stringify(data) + }; + }); + }, + interpretResponse: function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; + } + let bidResponses = []; + serverResponse.body.seatbid.forEach(seatbid => { + let bids = seatbid.bid || []; + bids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, + cpm: bid.price, + currency: bid.cur, + width: bid.w, + height: bid.h, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + ad: bid.adm, + meta: {} + }; + if (bid.meta && bid.meta.adomain && utils.isArray(bid.meta.adomain)) { + bidResponse.meta.advertiserDomains = bid.meta.adomain; + } + if (bid.mediaType === VIDEO) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = bid.adm; + } + bidResponses.push(bidResponse); + }); + }); + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + let gdprParams = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + gdprParams = `?consent_str=${gdprConsent.consentString}`; + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = gdprParams + `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + gdprParams = gdprParams + `&consent_given=` + kubientGetConsentGiven(gdprConsent); + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://kdmp.kbntx.ch/init.html' + gdprParams + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://kdmp.kbntx.ch/init.png' + gdprParams + }); + } + return syncs; + } +}; + +function kubientGetConsentGiven(gdprConsent) { + let consentGiven = 0; + if (typeof gdprConsent !== 'undefined') { + let apiVersion = utils.deepAccess(gdprConsent, `apiVersion`); + switch (apiVersion) { + case 1: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendorConsents.${VENDOR_ID}`) ? 1 : 0; + break; + case 2: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; + break; + } + } + return consentGiven; +} +registerBidder(spec); diff --git a/modules/kubientBidAdapter.md b/modules/kubientBidAdapter.md new file mode 100644 index 00000000000..a45d26dbc31 --- /dev/null +++ b/modules/kubientBidAdapter.md @@ -0,0 +1,61 @@ +# Overview +​ +**Module Name**: Kubient Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: artem.aleksashkin@kubient.com +​ +# Description +​ +Connects to Kubient KSSP demand source to fetch bids. Banners and Video supported +​ +# Banner Test Parameters +``` +var adUnits = [ + { + code: 'banner-ad-unit', + mediaTypes: { + banner: { + sizes: [[300, 100]] + } + }, + bids: [{ + bidder: 'kubient', + params: { + zoneid: "5fbb948f1e22b", + } + }] + } +]; +​ +# Video Test Parameters +​ +var adUnits = [ + { + code: 'video-ad-unit', + mediaTypes: { + video: { + playerSize: [300, 250], // required + context: 'instream', // required + mimes: ['video/mp4','video/x-flv'], // required + protocols: [ 2, 3 ], // required, set at least 1 value in array + placement: 1, // optional, defaults to 2 when context = outstream + api: [ 1, 2 ], // optional + skip: 0, // optional + minduration: 5, // optional + maxduration: 30, // optional + playbackmethod: [1,3], // optional + battr: [ 13, 14 ], // optional + linearity: 1, // optional + minbitrate: 10, // optional + maxbitrate: 10 // optional + } + }, + bids: [{ + bidder: 'kubient', + params: { + zoneid: "60ad1c0b35864", + } + }] + } +]; +``` diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js deleted file mode 100644 index 1ad660e5916..00000000000 --- a/modules/lemmaBidAdapter.js +++ /dev/null @@ -1,401 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -var BIDDER_CODE = 'lemma'; -var LOG_WARN_PREFIX = 'LEMMA: '; -var ENDPOINT = 'https://ads.lemmatechnologies.com/lemma/servad'; -var DEFAULT_CURRENCY = 'USD'; -var AUCTION_TYPE = 2; -var DEFAULT_TMAX = 300; -var DEFAULT_NET_REVENUE = false; - -export var spec = { - - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid: bid => { - if (bid && bid.params) { - if (!utils.isNumber(bid.params.pubId)) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be string. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); - return false; - } - if (!bid.params.adunitId) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: adUnitId is mandatory. 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)); - return false; - } - } - return true; - } - return false; - }, - buildRequests: (bidRequests, bidderRequest) => { - var refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - var conf = _initConf(refererInfo); - const request = oRTBTemplate(bidRequests, conf); - if (request.imp.length == 0) { - return; - } - setOtherParams(bidderRequest, request); - const endPoint = endPointURL(bidRequests); - return { - method: 'POST', - url: endPoint, - data: JSON.stringify(request), - }; - }, - interpretResponse: (response, request) => { - return parseRTBResponse(request, response.body); - }, -}; - -function _initConf(refererInfo) { - var conf = {}; - conf.pageURL = (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href; - if (refererInfo && refererInfo.referer) { - conf.refURL = refererInfo.referer; - } else { - conf.refURL = ''; - } - return conf; -} - -function parseRTBResponse(request, response) { - var bidResponses = []; - try { - if (response.seatbid) { - var currency = response.curr || DEFAULT_CURRENCY; - var seatbid = response.seatbid; - seatbid.forEach(seatbidder => { - var bidder = seatbidder.bid; - bidder.forEach(bid => { - var req = parse(request.data); - var newBid = { - requestId: bid.impid, - cpm: parseFloat(bid.price).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid, - currency: currency, - netRevenue: DEFAULT_NET_REVENUE, - ttl: 300, - referrer: req.site.ref, - ad: bid.adm - }; - if (bid.dealid) { - newBid.dealId = bid.dealid; - } - if (req.imp && req.imp.length > 0) { - newBid.mediaType = req.mediaType; - req.imp.forEach(robj => { - if (bid.impid === robj.id) { - switch (newBid.mediaType) { - case BANNER: - break; - case VIDEO: - newBid.width = bid.hasOwnProperty('w') ? bid.w : robj.video.w; - newBid.height = bid.hasOwnProperty('h') ? bid.h : robj.video.h; - newBid.vastXml = bid.adm; - break; - } - } - }); - } - bidResponses.push(newBid); - }); - }); - } - } catch (error) { - utils.logError(LOG_WARN_PREFIX, 'ERROR ', error); - } - return bidResponses; -} - -function oRTBTemplate(bidRequests, conf) { - try { - var oRTBObject = { - id: '' + new Date().getTime(), - at: AUCTION_TYPE, - tmax: DEFAULT_TMAX, - cur: [DEFAULT_CURRENCY], - imp: _getImpressionArray(bidRequests), - user: {}, - ext: {} - }; - var bid = bidRequests[0]; - var app = _getAppObject(bid); - var site = _getSiteObject(bid, conf); - var device = _getDeviceObject(bid); - if (app) { - oRTBObject.app = app; - } - if (site) { - oRTBObject.site = site; - } - if (device) { - oRTBObject.device = device; - } - return oRTBObject; - } catch (ex) { - utils.logError(LOG_WARN_PREFIX, 'ERROR ', ex); - } -} - -function _getImpressionArray(request) { - var impArray = []; - var map = request.map(bid => _getImpressionObject(bid)); - if (map) { - map.forEach(o => { - if (o) { - impArray.push(o); - } - }); - } - return impArray; -} - -function endPointURL(request) { - var params = request && request[0].params ? request[0].params : null; - if (params) { - var pubId = params.pubId ? params.pubId : 0; - var adunitId = params.adunitId ? params.adunitId : 0; - return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; - } - return null; -} - -function _getDomain(url) { - var a = document.createElement('a'); - a.setAttribute('href', url); - return a.hostname; -} - -function _getSiteObject(request, conf) { - var params = request && request.params ? request.params : null; - if (params) { - var pubId = params.pubId ? params.pubId : '0'; - var siteId = params.siteId ? params.siteId : '0'; - var appParams = params.app; - if (!appParams) { - return { - publisher: { - id: pubId.toString() - }, - domain: _getDomain(conf.pageURL), - id: siteId.toString(), - ref: conf.refURL, - page: conf.pageURL - }; - } - } - return null; -} - -function _getAppObject(request) { - var params = request && request.params ? request.params : null; - if (params) { - var pubId = params.pubId ? params.pubId : 0; - var appParams = params.app; - if (appParams) { - return { - publisher: { - id: pubId.toString(), - }, - id: appParams.id, - name: appParams.name, - bundle: appParams.bundle, - storeurl: appParams.storeUrl, - domain: appParams.domain, - cat: appParams.categories, - pagecat: appParams.page_category - }; - } - } - return null; -} - -function _getDeviceObject(request) { - var params = request && request.params ? request.params : null; - if (params) { - 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: { - country: params.country, - lat: params.latitude, - lon: params.longitude, - region: params.region, - city: params.city, - zip: params.zip - }, - ip: params.ip, - devicetype: params.device_type, - ifa: params.ifa, - }; - } - return null; -} - -function setOtherParams(request, ortbRequest) { - var params = request && request.params ? request.params : null; - if (request && request.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: request.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: request.gdprConsent.consentString } }; - } - if (params) { - ortbRequest.tmax = params.tmax; - ortbRequest.bcat = params.bcat; - } -} - -function _getSizes(request) { - if (request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) { - return request.sizes[0]; - } - return null; -} - -function _getBannerRequest(bid) { - var bObj; - var adFormat = []; - if (bid.mediaType === 'banner' || utils.deepAccess(bid, 'mediaTypes.banner')) { - var params = bid ? bid.params : null; - var bannerData = params.banner; - var sizes = _getSizes(bid) || []; - if (sizes && sizes.length == 0) { - sizes = bid.mediaTypes.banner.sizes[0]; - } - if (sizes && sizes.length > 0) { - bObj = {}; - bObj.w = sizes[0]; - bObj.h = sizes[1]; - bObj.pos = 0; - if (bannerData) { - bObj = utils.deepClone(bannerData); - } - sizes = bid.mediaTypes.banner.sizes; - if (sizes.length > 0) { - adFormat = []; - sizes.forEach(function(size) { - if (size.length > 1) { - adFormat.push({ w: size[0], h: size[1] }); - } - }); - if (adFormat.length > 0) { - bObj.format = adFormat; - } - } - } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.sizes missing for adunit: ' + bid.params.adunitId); - } - } - return bObj; -} - -function _getVideoRequest(bid) { - var vObj; - if (bid.mediaType === 'video' || utils.deepAccess(bid, 'mediaTypes.video')) { - var params = bid ? bid.params : null; - var sizes = _getSizes(bid) || []; - if (sizes && sizes.length == 0) { - sizes = bid.mediaTypes && bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : []; - } - if (sizes && sizes.length > 0) { - var videoData = params.video; - vObj = {}; - if (videoData) { - vObj = utils.deepClone(videoData); - } - vObj.w = sizes[0]; - vObj.h = sizes[1]; - } else { - utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.video.sizes missing for adunit: ' + bid.params.adunitId); - } - } - return vObj; -} - -function _getImpressionObject(bid) { - var impression = {}; - var bObj; - var vObj; - var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; - var mediaTypes = ''; - var format = []; - var params = bid && bid.params ? bid.params : null; - impression = { - id: bid.bidId, - tagid: params.adunitId ? params.adunitId.toString() : undefined, - secure: window.location.protocol === 'https:' ? 1 : 0, - bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY - }; - - if (params.bidFloor) { - impression.bidfloor = params.bidFloor; - } - - if (bid.hasOwnProperty('mediaTypes')) { - for (mediaTypes in bid.mediaTypes) { - switch (mediaTypes) { - case BANNER: - bObj = _getBannerRequest(bid); - if (bObj) { - impression.banner = bObj; - } - break; - case VIDEO: - vObj = _getVideoRequest(bid); - if (vObj) { - impression.video = vObj; - } - break; - } - } - } else { - bObj = { - pos: 0, - w: sizes && sizes[0] ? sizes[0][0] : 0, - h: sizes && sizes[0] ? sizes[0][1] : 0, - }; - if (utils.isArray(sizes) && sizes.length > 1) { - sizes = sizes.splice(1, sizes.length - 1); - sizes.forEach(size => { - format.push({ - w: size[0], - h: size[1] - }); - }); - bObj.format = format; - } - impression.banner = bObj; - } - - return impression.hasOwnProperty(BANNER) || - impression.hasOwnProperty(VIDEO) ? impression : undefined; -} - -function parse(rawResp) { - try { - if (rawResp) { - return JSON.parse(rawResp); - } - } catch (ex) { - utils.logError(LOG_WARN_PREFIX, 'ERROR', ex); - } - return null; -} - -registerBidder(spec); diff --git a/modules/lifestreetBidAdapter.js b/modules/lifestreetBidAdapter.js deleted file mode 100644 index 4317eb8b82e..00000000000 --- a/modules/lifestreetBidAdapter.js +++ /dev/null @@ -1,139 +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 = 'lifestreet'; -const ADAPTER_VERSION = '$prebid.version$'; - -const urlTemplate = template`https://ads.lfstmedia.com/gate/${'adapter'}/${'slot'}?adkey=${'adkey'}&ad_size=${'ad_size'}&__location=${'location'}&__referrer=${'referrer'}&__wn=${'wn'}&__sf=${'sf'}&__fif=${'fif'}&__if=${'if'}&__stamp=${'stamp'}&__pp=1&__hb=1&_prebid_json=1&__gz=1&deferred_format=vast_2_0,vast_3_0&__hbver=${'hbver'}`; - -/** - * A helper function for template to generate string from boolean - */ -function boolToString(value) { - return value ? '1' : '0'; -} - -/** - * A helper function to form URL from the template - */ -function template(strings, ...keys) { - return function(...values) { - let dict = values[values.length - 1] || {}; - let result = [strings[0]]; - keys.forEach(function(key, i) { - let value = utils.isInteger(key) ? values[key] : dict[key]; - result.push(value, strings[i + 1]); - }); - return result.join(''); - }; -} - -/** - * Creates a bid requests for a given bid. - * - * @param {BidRequest} bid The bid params to use for formatting a request - */ -function formatBidRequest(bid, bidderRequest = {}) { - const {params} = bid; - const {referer} = (bidderRequest.refererInfo || {}); - let url = urlTemplate({ - adapter: 'prebid', - slot: params.slot, - adkey: params.adkey, - ad_size: params.ad_size, - location: referer, - referrer: referer, - wn: boolToString(/fb_http/i.test(window.name)), - sf: boolToString(window['sfAPI'] || window['$sf']), - fif: boolToString(window['inDapIF'] === true), - if: boolToString(window !== window.top), - stamp: new Date().getTime(), - hbver: ADAPTER_VERSION - }); - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - const gdpr = '&__gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0'); - url += gdpr; - } - if (bidderRequest.gdprConsent.consentString !== undefined) { - url += `&__consent=${bidderRequest.gdprConsent.consentString}`; - } - } - - // ccpa support - if (bidderRequest.uspConsent) { - url += `&__us_privacy=${bidderRequest.uspConsent}` - } - - return { - method: 'GET', - url: url, - bidId: bid.bidId - }; -} - -function isResponseValid(response) { - return !/^\s*\{\s*"advertisementAvailable"\s*:\s*false/i.test(response.content) && - response.content.indexOf('') === -1 && (typeof response.cpm !== 'undefined') && - response.status === 1; -} - -export const spec = { - code: BIDDER_CODE, - aliases: ['lsm'], - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid: (bid = {}) => { - const {params = {}} = bid; - return !!(params.slot && params.adkey && params.ad_size); - }, - - buildRequests: (validBidRequests, bidderRequest) => { - return validBidRequests.map(bid => { - return formatBidRequest(bid, bidderRequest) - }); - }, - - interpretResponse: (serverResponse, bidRequest) => { - const bidResponses = []; - let response = serverResponse.body; - - if (!isResponseValid(response)) { - return bidResponses; - } - - const bidResponse = { - requestId: bidRequest.bidId, - cpm: response.cpm, - width: response.width, - height: response.height, - creativeId: response.creativeId, - currency: response.currency ? response.currency : 'USD', - netRevenue: response.netRevenue ? response.netRevenue : true, - ttl: response.ttl ? response.ttl : 86400 - }; - - if (response.hasOwnProperty('dealId')) { - bidResponse.dealId = response.dealId; - } - if (response.content_type.indexOf('vast') > -1) { - if (typeof response.vastUrl !== 'undefined') { - bidResponse.vastUrl = response.vastUrl; - } else { - bidResponse.vastXml = response.content; - } - - bidResponse.mediaType = VIDEO; - } else { - bidResponse.ad = response.content; - bidResponse.mediaType = BANNER; - } - - bidResponses.push(bidResponse); - return bidResponses; - } -}; - -registerBidder(spec); diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js new file mode 100644 index 00000000000..6203e22d253 --- /dev/null +++ b/modules/limelightDigitalBidAdapter.js @@ -0,0 +1,144 @@ +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 = 'limelightDigital'; + +/** + * 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 || !bid.meta.advertiserDomains) { + return false; + } + switch (bid.meta.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, + aliases: ['pll'], + 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: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.host && bid.params.adUnitType && + (bid.params.adUnitId || bid.params.adUnitId === 0)); + }, + + /** + * 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))); + }, + + /** + * 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: (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} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, bidRequest) => { + const bidResponses = []; + const serverBody = serverResponse.body; + const len = serverBody.length; + for (let i = 0; i < len; i++) { + const bidResponse = serverBody[i]; + if (isBidResponseValid(bidResponse)) { + bidResponses.push(bidResponse); + } + } + return bidResponses; + }, +}; + +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) { + let sizes; + if (bidRequest.mediaTypes) { + switch (bidRequest.params.adUnitType) { + case BANNER: + if (bidRequest.mediaTypes.banner && bidRequest.mediaTypes.banner.sizes) { + sizes = bidRequest.mediaTypes.banner.sizes; + } + break; + case VIDEO: + if (bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { + sizes = [bidRequest.mediaTypes.video.playerSize]; + } + break; + } + } + sizes = (sizes || []).concat(bidRequest.sizes || []).filter(utils.uniques); + return { + host: bidRequest.params.host, + adUnit: { + id: bidRequest.params.adUnitId, + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + sizes: sizes.map(size => { + return { + width: size[0], + height: size[1] + } + }), + type: bidRequest.params.adUnitType.toUpperCase() + } + } +} diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md new file mode 100644 index 00000000000..ab69ef8eaa4 --- /dev/null +++ b/modules/limelightDigitalBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: Limelight Digital Exchange Bidder Adapter +Module Type: Bidder Adapter +Maintainer: engineering@project-limelight.com +``` + +# Description + +Module that connects to Limelight Digital Exchange's demand sources + +# Test Parameters for banner +``` +var adUnits = [{ + code: 'placementCode', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.ortb.net', + adUnitId: 0, + adUnitType: 'banner' + } + }] +}]; +``` + +# Test Parameters for video +``` +var videoAdUnit = [{ + code: 'video1', + sizes: [[300, 250]], + bids: [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.ortb.net', + adUnitId: 0, + adUnitType: 'video' + } + }] +}]; +``` + +# Configuration + +The Limelight Digital Exchange Bidder 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/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index bd2638e5936..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -5,14 +5,33 @@ * @requires module:modules/userId */ import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import { triggerPixel } from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/cjs/live-connect.js'; -import { uspDataHandler } from '../src/adapterManager.js'; +import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js'; const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager(null, MODULE_NAME); +const calls = { + ajaxGet: (url, onSuccess, onError, timeout) => { + ajaxBuilder(timeout)( + url, + { + success: onSuccess, + error: onError + }, + undefined, + { + method: 'GET', + withCredentials: true + } + ) + }, + pixelGet: (url, onload) => triggerPixel(url, onload) +} let eventFired = false; let liveConnect = null; @@ -24,26 +43,18 @@ export function reset() { if (window && window.liQ) { window.liQ = []; } + liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - if (collectConfig) { - if (collectConfig.appId) { - config.appId = collectConfig.appId; - } - if (collectConfig.fpiStorageStrategy) { - config.storageStrategy = collectConfig.fpiStorageStrategy; - } - if (collectConfig.fpiExpirationDays) { - config.expirationDays = collectConfig.fpiExpirationDays; - } - if (collectConfig.collectorUrl) { - config.collectorUrl = collectConfig.collectorUrl; - } - } + collectConfig = collectConfig || {} + collectConfig.appId && (config.appId = collectConfig.appId); + collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); + collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); + collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); return config; } @@ -64,6 +75,9 @@ function initializeLiveConnect(configParams) { if (configParams.partner) { identityResolutionConfig.source = configParams.partner } + if (configParams.ajaxTimeout) { + identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout; + } const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); liveConnectConfig.wrapperName = 'prebid'; @@ -73,9 +87,18 @@ function initializeLiveConnect(configParams) { if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; } + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; + liveConnectConfig.gdprConsent = gdprConsent.consentString; + } - // The second param is the storage object, which means that all LS & Cookie manipulation will go through PBJS utils. - liveConnect = LiveConnect(liveConnectConfig, storage); + // The second param is the storage object, LS & Cookie manipulation uses PBJS utils. + // The third param is the ajax and pixel object, the ajax and pixel use PBJS utils. + liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); + if (configParams.emailHash) { + liveConnect.push({ hash: configParams.emailHash }) + } return liveConnect; } @@ -88,24 +111,33 @@ function tryFireEvent() { /** @type {Submodule} */ export const liveIntentIdSubmodule = { + moduleMode: process.env.LiveConnectMode, /** * used to link submodule with config * @type {string} */ name: MODULE_NAME, + setModuleMode(mode) { + this.moduleMode = mode + }, + getInitializer() { + return this.moduleMode === 'minimal' ? MinimalLiveConnect : LiveConnect + }, + /** * decode the stored id value for passing to bid requests. Note that lipb object is a wrapper for everything, and * internally it could contain more data other than `lipbid`(e.g. `segments`) depending on the `partner` and * `publisherId` params. * @function * @param {{unifiedId:string}} value - * @param {SubmoduleParams|undefined} [configParams] + * @param {SubmoduleConfig|undefined} config * @returns {{lipb:Object}} */ - decode(value, configParams) { + decode(value, config) { + const configParams = (config && config.params) || {}; function composeIdObject(value) { - const base = { 'lipbid': value['unifiedId'] }; + const base = { 'lipbid': value.unifiedId }; delete value.unifiedId; return { 'lipb': { ...base, ...value } }; } @@ -121,38 +153,29 @@ export const liveIntentIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { + const configParams = (config && config.params) || {}; const liveConnect = initializeLiveConnect(configParams); if (!liveConnect) { return; } tryFireEvent(); - // Don't do the internal ajax call, but use the composed url and fire it via PBJS ajax module - const url = liveConnect.resolutionCallUrl(); - const result = function (callback) { - const callbacks = { - success: response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + const result = function(callback) { + liveConnect.resolve( + response => { + callback(response); }, - error: error => { + error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); callback(); } - }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); - }; - return {callback: result}; + ) + } + + return { callback: result }; } }; diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index b331448161e..29b57e3dd97 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -34,6 +34,18 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE cache.auctions[args.auctionId].timeStamp = args.start; args.bids.forEach(function(bidRequest) { + cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; + cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; + let lwFloor; + + if (bidRequest.lwflr) { + lwFloor = bidRequest.lwflr.flr; + + let buyerFloor = bidRequest.lwflr.bflrs ? bidRequest.lwflr.bflrs[bidRequest.bidder] : undefined; + + lwFloor = buyerFloor || lwFloor; + } + cache.auctions[args.auctionId].bids[bidRequest.bidId] = { bidder: bidRequest.bidder, adUnit: bidRequest.adUnitCode, @@ -42,7 +54,12 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE timeout: false, sendStatus: 0, readyToSend: 0, - start: args.start + start: args.start, + lwFloor: lwFloor, + floorData: bidRequest.floorData, + auc: bidRequest.auc, + buc: bidRequest.buc, + lw: bidRequest.lw } utils.logInfo(bidRequest); @@ -59,7 +76,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bidResponse.cpm = args.cpm; bidResponse.ttr = args.timeToRespond; bidResponse.readyToSend = 1; - bidResponse.mediaType = args.mediaType == 'native' ? 2 : 1; + bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1); if (!bidResponse.ttr) { bidResponse.ttr = time - bidResponse.start; } @@ -67,6 +84,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE cache.auctions[args.auctionId].bidAdUnits[bidResponse.adUnit] = { sent: 0, + lw: bidResponse.lw, timeStamp: cache.auctions[args.auctionId].timeStamp }; } @@ -116,12 +134,15 @@ livewrappedAnalyticsAdapter.enableAnalytics = function (config) { }; livewrappedAnalyticsAdapter.sendEvents = function() { + var sentRequests = getSentRequests(); var events = { publisherId: initOptions.publisherId, - requests: getSentRequests(), - responses: getResponses(), - wins: getWins(), - timeouts: getTimeouts(), + gdpr: sentRequests.gdpr, + auctionIds: sentRequests.auctionIds, + requests: sentRequests.sentRequests, + responses: getResponses(sentRequests.gdpr, sentRequests.auctionIds), + wins: getWins(sentRequests.gdpr, sentRequests.auctionIds), + timeouts: getTimeouts(sentRequests.auctionIds), bidAdUnits: getbidAdUnits(), rcv: getAdblockerRecovered() }; @@ -144,10 +165,15 @@ function getAdblockerRecovered() { function getSentRequests() { var sentRequests = []; + var gdpr = []; + var auctionIds = []; Object.keys(cache.auctions).forEach(auctionId => { + let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { - let auction = cache.auctions[auctionId]; let bid = auction.bids[bidId]; if (!(bid.sendStatus & REQUESTSENT)) { bid.sendStatus |= REQUESTSENT; @@ -155,21 +181,29 @@ function getSentRequests() { sentRequests.push({ timeStamp: auction.timeStamp, adUnit: bid.adUnit, - bidder: bid.bidder + bidder: bid.bidder, + gdpr: gdprPos, + floor: bid.lwFloor, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw }); } }); }); - return sentRequests; + return {gdpr: gdpr, auctionIds: auctionIds, sentRequests: sentRequests}; } -function getResponses() { +function getResponses(gdpr, auctionIds) { var responses = []; Object.keys(cache.auctions).forEach(auctionId => { Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId) let bid = auction.bids[bidId]; if (bid.readyToSend && !(bid.sendStatus & RESPONSESENT) && !bid.timeout) { bid.sendStatus |= RESPONSESENT; @@ -183,7 +217,14 @@ function getResponses() { cpm: bid.cpm, ttr: bid.ttr, IsBid: bid.isBid, - mediaType: bid.mediaType + mediaType: bid.mediaType, + gdpr: gdprPos, + floor: bid.floorData ? bid.floorData.floorValue : bid.lwFloor, + floorCur: bid.floorData ? bid.floorData.floorCurrency : undefined, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw }); } }); @@ -192,13 +233,16 @@ function getResponses() { return responses; } -function getWins() { +function getWins(gdpr, auctionIds) { var wins = []; Object.keys(cache.auctions).forEach(auctionId => { Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); let bid = auction.bids[bidId]; + if (!(bid.sendStatus & WINSENT) && bid.won) { bid.sendStatus |= WINSENT; @@ -209,7 +253,14 @@ function getWins() { width: bid.width, height: bid.height, cpm: bid.cpm, - mediaType: bid.mediaType + mediaType: bid.mediaType, + gdpr: gdprPos, + floor: bid.floorData ? bid.floorData.floorValue : bid.lwFloor, + floorCur: bid.floorData ? bid.floorData.floorCurrency : undefined, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw }); } }); @@ -218,10 +269,42 @@ function getWins() { return wins; } -function getTimeouts() { +function getGdprPos(gdpr, auction) { + var gdprPos = 0; + for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) { + if (gdpr[gdprPos].gdprApplies == auction.gdprApplies && + gdpr[gdprPos].gdprConsent == auction.gdprConsent) { + break; + } + } + + if (gdprPos == gdpr.length) { + gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent}; + } + + return gdprPos; +} + +function getAuctionIdPos(auctionIds, auctionId) { + var auctionIdPos = 0; + for (auctionIdPos = 0; auctionIdPos < auctionIds.length; auctionIdPos++) { + if (auctionIds[auctionIdPos] == auctionId) { + break; + } + } + + if (auctionIdPos == auctionIds.length) { + auctionIds[auctionIdPos] = auctionId; + } + + return auctionIdPos; +} + +function getTimeouts(auctionIds) { var timeouts = []; Object.keys(cache.auctions).forEach(auctionId => { + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; let bid = auction.bids[bidId]; @@ -231,7 +314,11 @@ function getTimeouts() { timeouts.push({ bidder: bid.bidder, adUnit: bid.adUnit, - timeStamp: auction.timeStamp + timeStamp: auction.timeStamp, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw }); } }); @@ -252,7 +339,8 @@ function getbidAdUnits() { bidAdUnits.push({ adUnit: adUnit, - timeStamp: bidAdUnit.timeStamp + timeStamp: bidAdUnit.timeStamp, + lw: bidAdUnit.lw }); } }); diff --git a/modules/livewrappedAnalyticsAdapter.md b/modules/livewrappedAnalyticsAdapter.md new file mode 100644 index 00000000000..de4f352aa19 --- /dev/null +++ b/modules/livewrappedAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: Livewrapped Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: info@livewrapped.com + +# Description + +Analytics adapter for Livewrapped AB. In order to use the adapter, please contact Livewrapped AB. + +# Test Parameters + +``` +{ + provider: 'livewrapped', + options : { + publisherId: "64c01620-fa98-4936-9794-6001d8ebfdb0" + } +} + +``` diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 78b29ab5016..93552638007 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -2,18 +2,19 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; export const storage = getStorageManager(); const BIDDER_CODE = 'livewrapped'; export const URL = 'https://lwadm.com/ad'; -const VERSION = '1.3'; +const VERSION = '1.4'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + gvlid: 919, /** * Determines whether or not the given bid request is valid. @@ -80,6 +81,8 @@ export const spec = { version: VERSION, gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined, gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined, + coppa: getCoppa(), + usPrivacy: bidderRequest.uspConsent, cookieSupport: !utils.isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], @@ -129,7 +132,12 @@ export const spec = { if (ad.native) { bidResponse.native = ad.native; - bidResponse.mediaType = NATIVE + bidResponse.mediaType = NATIVE; + } + + if (ad.video) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = ad.tag; } bidResponses.push(bidResponse); @@ -216,9 +224,15 @@ function bidToAdRequest(bid) { options: bid.params.options }; + if (bid.auc !== undefined) { + adRequest.auc = bid.auc; + } + adRequest.native = utils.deepAccess(bid, 'mediaTypes.native'); - if (adRequest.native && utils.deepAccess(bid, 'mediaTypes.banner')) { + adRequest.video = utils.deepAccess(bid, 'mediaTypes.video'); + + if ((adRequest.native || adRequest.video) && utils.deepAccess(bid, 'mediaTypes.banner')) { adRequest.banner = true; } @@ -247,33 +261,10 @@ function getAdblockerRecovered() { } catch (e) {} } -function AddExternalUserId(eids, value, source, atype, rtiPartner) { - if (utils.isStr(value)) { - var eid = { - source, - uids: [{ - id: value, - atype - }] - }; - - if (rtiPartner) { - eid.uids[0] = {ext: {rtiPartner}}; - } - - eids.push(eid); - } -} - function handleEids(bidRequests) { - let eids = []; const bidRequest = bidRequests[0]; - if (bidRequest && bidRequest.userId) { - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); // Also add this to eids - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); - } - if (eids.length > 0) { - return {user: {ext: {eids}}}; + if (bidRequest && bidRequest.userIdAsEids) { + return {user: {ext: {eids: bidRequest.userIdAsEids}}}; } return undefined; @@ -320,4 +311,9 @@ function getDeviceHeight() { return window.innerHeight; } +function getCoppa() { + if (typeof config.getConfig('coppa') === 'boolean') { + return config.getConfig('coppa'); + } +} registerBidder(spec); diff --git a/modules/livewrappedBidAdapter.md b/modules/livewrappedBidAdapter.md index 15e3e8d533a..10fc2a4778a 100644 --- a/modules/livewrappedBidAdapter.md +++ b/modules/livewrappedBidAdapter.md @@ -8,7 +8,7 @@ Connects to Livewrapped Header Bidding wrapper for bids. -Livewrapped supports banner. +Livewrapped supports banner, native and video. # Test Parameters @@ -20,7 +20,7 @@ var adUnits = [ bids: [{ bidder: 'livewrapped', params: { - adUnitId: '6A32352E-BC17-4B94-B2A7-5BF1724417D7' + adUnitId: 'D801852A-681F-11E8-86A7-0A44794250D4' } }] } diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js deleted file mode 100644 index 51d5c48e1fc..00000000000 --- a/modules/lkqdBidAdapter.js +++ /dev/null @@ -1,274 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'lkqd'; -const BID_TTL_DEFAULT = 300; -const ENDPOINT = 'https://v.lkqd.net/ad'; - -const PARAM_OUTPUT_DEFAULT = 'vast'; -const PARAM_EXECUTION_DEFAULT = 'any'; -const PARAM_SUPPORT_DEFAULT = 'html5'; -const PARAM_PLAYINIT_DEFAULT = 'auto'; -const PARAM_VOLUME_DEFAULT = '100'; - -function _validateId(id) { - if (id && typeof id !== 'undefined' && parseInt(id) > 0) { - return true; - } - - return false; -} - -function isBidRequestValid(bidRequest) { - if (bidRequest.bidder === BIDDER_CODE && typeof bidRequest.params !== 'undefined') { - if (_validateId(bidRequest.params.siteId) && _validateId(bidRequest.params.placementId)) { - return true; - } - } - - return false; -} - -function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; - - for (let i = 0; i < validBidRequests.length; i++) { - let bidRequest = validBidRequests[i]; - - let sizes = []; - // if width/height not provided to the ad unit for some reason then attempt request with default 640x480 size - let bidRequestSizes = bidRequest.sizes; - let bidRequestDeepSizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); - if ((!bidRequestSizes || !bidRequestSizes.length) && (!bidRequestDeepSizes || !bidRequestDeepSizes.length)) { - utils.logWarn('Warning: Could not find valid width/height parameters on the provided adUnit'); - sizes = [[640, 480]]; - } - - // JWPlayer demo page uses sizes: [640,480] instead of sizes: [[640,480]] so need to handle single-layer array as well as nested arrays - if (bidRequestSizes && bidRequestSizes.length > 0) { - sizes = bidRequestSizes; - if (bidRequestSizes.length === 2 && typeof bidRequestSizes[0] === 'number' && typeof bidRequestSizes[1] === 'number') { - sizes = [bidRequestSizes]; - } - } else if (bidRequestDeepSizes && bidRequestDeepSizes.length > 0) { - sizes = bidRequestDeepSizes; - if (bidRequestDeepSizes.length === 2 && typeof bidRequestDeepSizes[0] === 'number' && typeof bidRequestDeepSizes[1] === 'number') { - sizes = [bidRequestDeepSizes]; - } - } - - 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 { - utils.logWarn('Warning: Could not determine width/height from the provided adUnit'); - } - - let sspUrl = ENDPOINT.concat(); - let sspData = {}; - - // required parameters - sspData.pid = bidRequest.params.placementId; - sspData.sid = bidRequest.params.siteId; - sspData.prebid = true; - - // optional parameters - if (bidRequest.params.hasOwnProperty('output') && bidRequest.params.output != null) { - sspData.output = bidRequest.params.output; - } else { - sspData.output = PARAM_OUTPUT_DEFAULT; - } - if (bidRequest.params.hasOwnProperty('execution') && bidRequest.params.execution != null) { - sspData.execution = bidRequest.params.execution; - } else { - sspData.execution = PARAM_EXECUTION_DEFAULT; - } - if (bidRequest.params.hasOwnProperty('support') && bidRequest.params.support != null) { - sspData.support = bidRequest.params.support; - } else { - sspData.support = PARAM_SUPPORT_DEFAULT; - } - if (bidRequest.params.hasOwnProperty('playinit') && bidRequest.params.playinit != null) { - sspData.playinit = bidRequest.params.playinit; - } else { - sspData.playinit = PARAM_PLAYINIT_DEFAULT; - } - if (bidRequest.params.hasOwnProperty('volume') && bidRequest.params.volume != null) { - sspData.volume = bidRequest.params.volume; - } else { - sspData.volume = PARAM_VOLUME_DEFAULT; - } - if (playerWidth) { - sspData.width = playerWidth; - } - if (playerHeight) { - sspData.height = playerHeight; - } - if (bidRequest.params.hasOwnProperty('vpaidmode') && bidRequest.params.vpaidmode != null) { - sspData.vpaidmode = bidRequest.params.vpaidmode; - } - if (bidRequest.params.hasOwnProperty('appname') && bidRequest.params.appname != null) { - sspData.appname = bidRequest.params.appname; - } - if (bidRequest.params.hasOwnProperty('bundleid') && bidRequest.params.bundleid != null) { - sspData.bundleid = bidRequest.params.bundleid; - } - if (bidRequest.params.hasOwnProperty('aid') && bidRequest.params.aid != null) { - sspData.aid = bidRequest.params.aid; - } - if (bidRequest.params.hasOwnProperty('idfa') && bidRequest.params.idfa != null) { - sspData.idfa = bidRequest.params.idfa; - } - if (bidRequest.params.hasOwnProperty('gdpr') && bidRequest.params.gdpr != null) { - sspData.gdpr = bidRequest.params.gdpr; - } - if (bidRequest.params.hasOwnProperty('gdprcs') && bidRequest.params.gdprcs != null) { - sspData.gdprcs = bidRequest.params.gdprcs; - } - if (bidRequest.params.hasOwnProperty('flrd') && bidRequest.params.flrd != null) { - sspData.flrd = bidRequest.params.flrd; - } - if (bidRequest.params.hasOwnProperty('flrmp') && bidRequest.params.flrmp != null) { - sspData.flrmp = bidRequest.params.flrmp; - } - if (bidRequest.params.hasOwnProperty('schain') && bidRequest.params.schain != null) { - sspData.schain = bidRequest.params.schain; - } - if (bidRequest.params.hasOwnProperty('placement') && bidRequest.params.placement != null) { - sspData.placement = bidRequest.params.placement; - } - if (bidRequest.params.hasOwnProperty('timeout') && bidRequest.params.timeout != null) { - sspData.timeout = bidRequest.params.timeout; - } - if (bidRequest.params.hasOwnProperty('dnt') && bidRequest.params.dnt != null) { - sspData.dnt = bidRequest.params.dnt; - } - if (bidRequest.params.hasOwnProperty('pageurl') && bidRequest.params.pageurl != null) { - sspData.pageurl = bidRequest.params.pageurl; - } else if (bidderRequest && bidderRequest.refererInfo) { - sspData.pageurl = encodeURIComponent(encodeURIComponent(bidderRequest.refererInfo.referer)); - } - if (bidRequest.params.hasOwnProperty('contentId') && bidRequest.params.contentId != null) { - sspData.contentid = bidRequest.params.contentId; - } - if (bidRequest.params.hasOwnProperty('contentTitle') && bidRequest.params.contentTitle != null) { - sspData.contenttitle = bidRequest.params.contentTitle; - } - if (bidRequest.params.hasOwnProperty('contentLength') && bidRequest.params.contentLength != null) { - sspData.contentlength = bidRequest.params.contentLength; - } - if (bidRequest.params.hasOwnProperty('contentUrl') && bidRequest.params.contentUrl != null) { - sspData.contenturl = bidRequest.params.contentUrl; - } - if (bidRequest.params.hasOwnProperty('schain') && bidRequest.params.schain) { - sspData.schain = bidRequest.params.schain; - } - - // random number to prevent caching - sspData.rnd = Math.floor(Math.random() * 999999999); - - // Prebid.js required properties - sspData.bidId = bidRequest.bidId; - sspData.bidWidth = playerWidth; - sspData.bidHeight = playerHeight; - - bidRequests.push({ - method: 'GET', - url: sspUrl, - data: Object.keys(sspData).map(function (key) { return key + '=' + sspData[key] }).join('&') + '&' - }); - } - } - - return bidRequests; -} - -function interpretResponse(serverResponse, bidRequest) { - let bidResponses = []; - if (serverResponse && serverResponse.body) { - if (serverResponse.error) { - utils.logError('Error: ' + serverResponse.error); - return bidResponses; - } else { - try { - let bidResponse = {}; - if (bidRequest && bidRequest.data && typeof bidRequest.data === 'string') { - let sspData; - let sspBidId; - let sspBidWidth; - let sspBidHeight; - if (window.URLSearchParams) { - sspData = new URLSearchParams(bidRequest.data); - sspBidId = sspData.get('bidId'); - sspBidWidth = sspData.get('bidWidth'); - sspBidHeight = sspData.get('bidHeight'); - } else { - if (bidRequest.data.indexOf('bidId=') >= 0) { - sspBidId = bidRequest.data.substr(bidRequest.data.indexOf('bidId=') + 6, bidRequest.data.length); - sspBidId = sspBidId.split('&')[0]; - } - if (bidRequest.data.indexOf('bidWidth=') >= 0) { - sspBidWidth = bidRequest.data.substr(bidRequest.data.indexOf('bidWidth=') + 9, bidRequest.data.length); - sspBidWidth = sspBidWidth.split('&')[0]; - } - if (bidRequest.data.indexOf('bidHeight=') >= 0) { - sspBidHeight = bidRequest.data.substr(bidRequest.data.indexOf('bidHeight=') + 10, bidRequest.data.length); - sspBidHeight = sspBidHeight.split('&')[0]; - } - } - - if (sspBidId) { - let sspXmlString = serverResponse.body; - let sspXml = new window.DOMParser().parseFromString(sspXmlString, 'text/xml'); - if (sspXml && sspXml.getElementsByTagName('parsererror').length == 0) { - let sspUrl = bidRequest.url.concat(); - - bidResponse.requestId = sspBidId; - bidResponse.bidderCode = BIDDER_CODE; - bidResponse.ad = ''; - bidResponse.cpm = parseFloat(sspXml.getElementsByTagName('Pricing')[0].textContent); - bidResponse.width = sspBidWidth; - bidResponse.height = sspBidHeight; - bidResponse.ttl = BID_TTL_DEFAULT; - bidResponse.creativeId = sspXml.getElementsByTagName('Ad')[0].getAttribute('id'); - bidResponse.currency = sspXml.getElementsByTagName('Pricing')[0].getAttribute('currency'); - bidResponse.netRevenue = true; - bidResponse.vastUrl = sspUrl; - bidResponse.vastXml = sspXmlString; - bidResponse.mediaType = VIDEO; - - bidResponses.push(bidResponse); - } else { - utils.logError('Error: Server response contained invalid XML'); - } - } else { - utils.logError('Error: Could not associate bid request to server response'); - } - } else { - utils.logError('Error: Could not associate bid request to server response'); - } - } catch (e) { - utils.logError('Error: Could not interpret server response'); - } - } - } else { - utils.logError('Error: No server response or server response was empty for the requested URL'); - } - - return bidResponses; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse -} - -registerBidder(spec); diff --git a/modules/lockerdomeBidAdapter.js b/modules/lockerdomeBidAdapter.js index 80c40b39f9a..4e30519c6d3 100644 --- a/modules/lockerdomeBidAdapter.js +++ b/modules/lockerdomeBidAdapter.js @@ -66,7 +66,10 @@ export const spec = { currency: bid.currency, netRevenue: bid.netRevenue, ad: bid.ad, - ttl: bid.ttl + ttl: bid.ttl, + meta: { + advertiserDomains: bid.adomain && Array.isArray(bid.adomain) ? bid.adomain : [] + } }; }); }, diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js new file mode 100644 index 00000000000..ae6d7a344d3 --- /dev/null +++ b/modules/loganBidAdapter.js @@ -0,0 +1,159 @@ +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 = 'logan'; +const AD_URL = 'https://USeast2.logan.ai/pbjs'; +const SYNC_URL = 'https://ssp-cookie.logan.ai' + +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.vastXml || bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.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 && 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 || {}, + bidfloor: getBidFloor(bid) + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.adFormat = BANNER; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.minduration = mediaType[VIDEO].minduration; + placement.maxduration = mediaType[VIDEO].maxduration; + placement.mimes = mediaType[VIDEO].mimes; + placement.protocols = mediaType[VIDEO].protocols; + placement.startdelay = mediaType[VIDEO].startdelay; + placement.placement = mediaType[VIDEO].placement; + placement.skip = mediaType[VIDEO].skip; + placement.skipafter = mediaType[VIDEO].skipafter; + placement.minbitrate = mediaType[VIDEO].minbitrate; + placement.maxbitrate = mediaType[VIDEO].maxbitrate; + placement.delivery = mediaType[VIDEO].delivery; + placement.playbackmethod = mediaType[VIDEO].playbackmethod; + placement.api = mediaType[VIDEO].api; + placement.linearity = mediaType[VIDEO].linearity; + placement.adFormat = VIDEO; + } else if (mediaType && mediaType[NATIVE]) { + placement.native = mediaType[NATIVE]; + placement.adFormat = 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)) { + 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/loganBidAdapter.md b/modules/loganBidAdapter.md new file mode 100644 index 00000000000..a4e082e47c2 --- /dev/null +++ b/modules/loganBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: logan Bidder Adapter +Module Type: logan Bidder Adapter +Maintainer: support@logan.ai +``` + +# Description + +Module that connects to logan demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 'testBanner' + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 'testVideo' + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 'testNative' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 74c00ee39d6..2c919f9c157 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -1,12 +1,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; const BIDDER_CODE = 'logicad'; const ENDPOINT_URL = 'https://pb.ladsp.com/adrequest/prebid'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE], isBidRequestValid: function (bid) { return !!(bid.params && bid.params.tid); }, @@ -62,6 +62,7 @@ function newBidRequest(bid, bidderRequest) { prebidJsVersion: '$prebid.version$', referrer: bidderRequest.refererInfo.referer, auctionStartTime: bidderRequest.auctionStart, + eids: bid.userIdAsEids, }; } diff --git a/modules/logicadBidAdapter.md b/modules/logicadBidAdapter.md index 32d40a7d3cd..de439f3f8c2 100644 --- a/modules/logicadBidAdapter.md +++ b/modules/logicadBidAdapter.md @@ -11,15 +11,48 @@ Currently module supports only banner mediaType. # Test Parameters ``` - var adUnits = [{ - code: 'test-code', - sizes: [[300, 250],[300, 600]], - bids: [{ - bidder: 'logicad', - params: { - tid: 'test', - page: 'url', - } - }] - }]; +var adUnits = [ + // Banner adUnit + { + code: 'test-code', + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'logicad', + params: { + tid: 'lfp-test-banner', + page: 'url' + } + }] + }, + // Native adUnit + { + code: 'test-native-code', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'logicad', + params: { + tid: 'lfp-test-native', + page: 'url' + } + }] + } +]; ``` diff --git a/modules/loopmeBidAdapter.js b/modules/loopmeBidAdapter.js deleted file mode 100644 index 919e0b17294..00000000000 --- a/modules/loopmeBidAdapter.js +++ /dev/null @@ -1,149 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { Renderer } from '../src/Renderer.js'; - -const LOOPME_ENDPOINT = 'https://loopme.me/api/hb'; - -const entries = (obj) => { - let output = []; - for (let key in obj) { - if (obj.hasOwnProperty(key)) { - output.push([key, obj[key]]) - } - } - return output; -} - -export const spec = { - code: 'loopme', - supportedMediaTypes: [BANNER, VIDEO], - /** - * @param {object} bid - * @return boolean - */ - isBidRequestValid: function(bid) { - if (typeof bid.params !== 'object') { - return false; - } - - return !!bid.params.ak; - }, - /** - * @param {BidRequest[]} bidRequests - * @param bidderRequest - * @return ServerRequest[] - */ - buildRequests: function(bidRequests, bidderRequest) { - return bidRequests.map(bidRequest => { - bidRequest.startTime = new Date().getTime(); - let payload = bidRequest.params; - - if (bidderRequest && bidderRequest.gdprConsent) { - payload.user_consent = bidderRequest.gdprConsent.consentString; - } - - let queryString = entries(payload) - .map(item => `${item[0]}=${encodeURI(item[1])}`) - .join('&'); - - const adUnitSizes = bidRequest.mediaTypes[BANNER] - ? utils.getAdUnitSizes(bidRequest) - : utils.deepAccess(bidRequest.mediaTypes, 'video.playerSize'); - - const sizes = - '&sizes=' + - adUnitSizes - .map(size => `${size[0]}x${size[1]}`) - .join('&sizes='); - - queryString = `${queryString}${sizes}${bidRequest.mediaTypes[VIDEO] ? '&media_type=video' : ''}`; - - return { - method: 'GET', - url: `${LOOPME_ENDPOINT}`, - options: { withCredentials: false }, - bidId: bidRequest.bidId, - data: queryString - }; - }); - }, - /** - * @param {*} responseObj - * @param {BidRequest} bidRequest - * @return {Bid[]} An array of bids which - */ - interpretResponse: function(response = {}, bidRequest) { - const responseObj = response.body; - if ( - responseObj === null || - typeof responseObj !== 'object' - ) { - return []; - } - - if ( - !responseObj.hasOwnProperty('ad') && - !responseObj.hasOwnProperty('vastUrl') - ) { - return []; - } - // responseObj.vastUrl = 'https://rawgit.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%201-2.0%20Samples/Inline_NonLinear_Verification_VAST2.0.xml'; - if (responseObj.vastUrl) { - const renderer = Renderer.install({ - id: bidRequest.bidId, - url: 'https://i.loopme.me/html/vast/loopme_flex.js', - loaded: false - }); - renderer.setRender((bid) => { - renderer.push(function () { - var adverts = [{ - 'type': 'VAST', - 'url': bid.vastUrl, - 'autoClose': -1 - }]; - var config = { - containerId: bid.adUnitCode, - vastTimeout: 250, - ads: adverts, - user_consent: '%%USER_CONSENT%%', - }; - window.L.flex.loader.load(config); - }) - }); - return [ - { - requestId: bidRequest.bidId, - cpm: responseObj.cpm, - width: responseObj.width, - height: responseObj.height, - ttl: responseObj.ttl, - currency: responseObj.currency, - creativeId: responseObj.creativeId, - dealId: responseObj.dealId, - netRevenue: responseObj.netRevenue, - vastUrl: responseObj.vastUrl, - mediaType: VIDEO, - renderer - } - ]; - } - - return [ - { - requestId: bidRequest.bidId, - cpm: responseObj.cpm, - width: responseObj.width, - height: responseObj.height, - ad: responseObj.ad, - ttl: responseObj.ttl, - currency: responseObj.currency, - creativeId: responseObj.creativeId, - dealId: responseObj.dealId, - netRevenue: responseObj.netRevenue, - mediaType: BANNER - } - ]; - } -}; -registerBidder(spec); diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index cdf9131dd68..6d36687b4e8 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -16,8 +16,11 @@ const MODULE_NAME = 'lotamePanoramaId'; const NINE_MONTHS_MS = 23328000 * 1000; const DAYS_TO_CACHE = 7; const DAY_MS = 60 * 60 * 24 * 1000; +const MISSING_CORE_CONSENT = 111; +const GVLID = 95; -export const storage = getStorageManager(null, MODULE_NAME); +export const storage = getStorageManager(GVLID, MODULE_NAME); +let cookieDomain; /** * Set the Lotame First Party Profile ID in the first party namespace @@ -26,7 +29,14 @@ export const storage = getStorageManager(null, MODULE_NAME); function setProfileId(profileId) { if (storage.cookiesAreEnabled()) { let expirationDate = new Date(utils.timestamp() + NINE_MONTHS_MS).toUTCString(); - storage.setCookie(KEY_PROFILE, profileId, expirationDate, 'Lax', undefined, undefined); + storage.setCookie( + KEY_PROFILE, + profileId, + expirationDate, + 'Lax', + cookieDomain, + undefined + ); } if (storage.hasLocalStorage()) { storage.setDataInLocalStorage(KEY_PROFILE, profileId, undefined); @@ -88,7 +98,7 @@ function saveLotameCache( value, expirationDate, 'Lax', - undefined, + cookieDomain, undefined ); } @@ -115,7 +125,7 @@ function getLotameLocalCache() { try { const rawExpiry = getFromStorage(KEY_EXPIRY); if (utils.isStr(rawExpiry)) { - cache.expiryTimestampMs = parseInt(rawExpiry, 0); + cache.expiryTimestampMs = parseInt(rawExpiry, 10); } } catch (error) { utils.logError(error); @@ -132,7 +142,14 @@ function clearLotameCache(key) { if (key) { if (storage.cookiesAreEnabled()) { let expirationDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expirationDate, 'Lax', undefined, undefined); + storage.setCookie( + key, + '', + expirationDate, + 'Lax', + cookieDomain, + undefined + ); } if (storage.hasLocalStorage()) { storage.removeDataFromLocalStorage(key, undefined); @@ -141,39 +158,46 @@ function clearLotameCache(key) { } /** @type {Submodule} */ export const lotamePanoramaIdSubmodule = { - /** * used to link submodule with config * @type {string} */ name: MODULE_NAME, + /** + * Vendor id of Lotame + * @type {Number} + */ + gvlid: GVLID, + /** * Decode the stored id value for passing to bid requests * @function decode * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config * @returns {(Object|undefined)} */ - decode(value, configParams) { - return utils.isStr(value) ? { 'lotamePanoramaId': value } : undefined; + decode(value, config) { + return utils.isStr(value) ? { lotamePanoramaId: value } : undefined; }, /** * Retrieve the Lotame Panorama Id * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams, consentData, cacheIdObj) { + getId(config, consentData, cacheIdObj) { + cookieDomain = lotamePanoramaIdSubmodule.findRootDomain(); let localCache = getLotameLocalCache(); let refreshNeeded = Date.now() > localCache.expiryTimestampMs; if (!refreshNeeded) { return { - id: localCache.data + id: localCache.data, }; } @@ -182,14 +206,25 @@ export const lotamePanoramaIdSubmodule = { const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { - queryParams.fp = storedUserId + queryParams.fp = storedUserId; } - if (consentData && utils.isBoolean(consentData.gdprApplies)) { - queryParams.gdpr_applies = consentData.gdprApplies; - if (consentData.gdprApplies) { - queryParams.gdpr_consent = consentData.consentString; + let consentString; + if (consentData) { + if (utils.isBoolean(consentData.gdprApplies)) { + queryParams.gdpr_applies = consentData.gdprApplies; } + consentString = consentData.consentString; + } + // If no consent string, try to read it from 1st party cookies + if (!consentString) { + consentString = getFromStorage('eupubconsent-v2'); + } + if (!consentString) { + consentString = getFromStorage('euconsent-v2'); + } + if (consentString) { + queryParams.gdpr_consent = consentString; } const url = utils.buildUrl({ protocol: 'https', @@ -204,10 +239,17 @@ export const lotamePanoramaIdSubmodule = { if (response) { try { let responseObj = JSON.parse(response); - saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts); + const shouldUpdateProfileId = !( + utils.isArray(responseObj.errors) && + responseObj.errors.indexOf(MISSING_CORE_CONSENT) !== -1 + ); + + saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts, responseObj.expiry_ts); if (utils.isStr(responseObj.profile_id)) { - setProfileId(responseObj.profile_id); + if (shouldUpdateProfileId) { + setProfileId(responseObj.profile_id); + } if (utils.isStr(responseObj.core_id)) { saveLotameCache( @@ -220,7 +262,9 @@ export const lotamePanoramaIdSubmodule = { clearLotameCache(KEY_ID); } } else { - clearLotameCache(KEY_PROFILE); + if (shouldUpdateProfileId) { + clearLotameCache(KEY_PROFILE); + } clearLotameCache(KEY_ID); } } catch (error) { @@ -232,7 +276,7 @@ export const lotamePanoramaIdSubmodule = { undefined, { method: 'GET', - withCredentials: true + withCredentials: true, } ); }; diff --git a/modules/lunamediaBidAdapter.js b/modules/lunamediaBidAdapter.js deleted file mode 100755 index 83be806af17..00000000000 --- a/modules/lunamediaBidAdapter.js +++ /dev/null @@ -1,383 +0,0 @@ -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 = 'lunamedia'; - -export const VIDEO_ENDPOINT = 'https://api.lunamedia.io/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; -export const BANNER_ENDPOINT = 'https://api.lunamedia.io/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 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, - 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, - 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(); - - let sizes = getVideoSizes(bid); - let 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(); - - let 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); diff --git a/modules/lunamediaBidAdapter.md b/modules/lunamediaBidAdapter.md index d0314d0fa41..ff5cc86c462 100755 --- a/modules/lunamediaBidAdapter.md +++ b/modules/lunamediaBidAdapter.md @@ -29,7 +29,8 @@ var displayAdUnit = [ bidder: 'lunamedia', params: { pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234 + placement: 1234, + size: "320x50" } }] }]; @@ -52,7 +53,8 @@ var videoAdUnit = { bidder: 'lunamedia', params: { pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, + placement: 1234, + size: "320x480", video: { id: 123, skip: 1, diff --git a/modules/e_volutionBidAdapter.js b/modules/lunamediahbBidAdapter.js similarity index 67% rename from modules/e_volutionBidAdapter.js rename to modules/lunamediahbBidAdapter.js index 9fc7035db32..1376d0c1714 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,11 +1,9 @@ -import {registerBidder} from '../src/adapters/bidderFactory.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 = 'e_volution'; -const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; -const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; -const NO_SYNC = true; +const BIDDER_CODE = 'lunamediahb'; +const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -18,7 +16,7 @@ function isBidResponseValid(bid) { case VIDEO: return Boolean(bid.vastUrl); case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + return Boolean(bid.native && bid.native.impressionTrackers); default: return false; } @@ -27,7 +25,6 @@ function isBidResponseValid(bid) { 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))); @@ -43,8 +40,9 @@ export const spec = { location = winTop.location; utils.logMessage(e); }; - let placements = []; - let request = { + + const placements = []; + const request = { 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', @@ -53,6 +51,7 @@ export const spec = { 'page': location.pathname, 'placements': placements }; + if (bidderRequest) { if (bidderRequest.uspConsent) { request.ccpa = bidderRequest.uspConsent; @@ -61,22 +60,31 @@ export const spec = { request.gdpr = bidderRequest.gdprConsent } } - const len = validBidRequests.length; + const len = validBidRequests.length; for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER - - placements.push({ + const bid = validBidRequests[i]; + const placement = { 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; + 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, @@ -94,18 +102,6 @@ export const spec = { } return response; }, - - getUserSyncs: (syncOptions, serverResponses) => { - if (NO_SYNC) { - return false - } else { - return [{ - type: 'image', - url: URL_SYNC - }]; - } - } - }; registerBidder(spec); diff --git a/modules/lunamediahbBidAdapter.md b/modules/lunamediahbBidAdapter.md new file mode 100644 index 00000000000..184dd846a9d --- /dev/null +++ b/modules/lunamediahbBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: lunamedia Bidder Adapter +Module Type: lunamedia Bidder Adapter +Maintainer: support@lunamedia.io +``` + +# Description + +Module that connects to lunamedia demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + } + ]; +``` diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js deleted file mode 100644 index 0a2eed0ad13..00000000000 --- a/modules/luponmediaBidAdapter.js +++ /dev/null @@ -1,374 +0,0 @@ -import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER} from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'luponmedia'; -const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; - -const DIGITRUST_PROP_NAMES = { - PREBID_SERVER: { - id: 'id', - keyv: 'keyv' - } -}; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.siteId && bid.params.keyId); // TODO: check for siteId and keyId - }, - buildRequests: function (bidRequests, bidderRequest) { - const bRequest = { - method: 'POST', - url: ENDPOINT_URL, - data: null, - options: {}, - bidderRequest - }; - - let currentImps = []; - - for (let i = 0, len = bidRequests.length; i < len; i++) { - let newReq = newOrtbBidRequest(bidRequests[i], bidderRequest, currentImps); - currentImps = newReq.imp; - bRequest.data = JSON.stringify(newReq); - } - - return bRequest; - }, - interpretResponse: (response, request) => { - const bidResponses = []; - var respCur = 'USD'; - 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)) { - // Supporting multiple bid responses for same adSize - respCur = response.body.cur || respCur; - response.body.seatbid.forEach(seatbidder => { - seatbidder.bid && - utils.isArray(seatbidder.bid) && - seatbidder.bid.forEach(bid => { - let newBid = { - requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid || bid.id, - dealId: bid.dealid, - currency: respCur, - netRevenue: false, - ttl: 300, - referrer: parsedReferrer, - ad: bid.adm - }; - - bidResponses.push(newBid); - }); - }); - } - } catch (error) { - utils.logError(error); - } - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - let allUserSyncs = []; - if (!hasSynced && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - responses.forEach(csResp => { - if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { - try { - let response = csResp.body.ext.usersyncs - let bidders = response.bidder_status; - for (let synci in bidders) { - let thisSync = bidders[synci]; - if (thisSync.no_cookie) { - let url = thisSync.usersync.url; - let type = thisSync.usersync.type; - - if (!url) { - utils.logError(`No sync url for bidder luponmedia.`); - } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { - utils.logMessage(`Invoking image pixel user sync for luponmedia`); - allUserSyncs.push({type: 'image', url: url}); - } else if (type == 'iframe' && syncOptions.iframeEnabled) { - utils.logMessage(`Invoking iframe user sync for luponmedia`); - allUserSyncs.push({type: 'iframe', url: url}); - } else { - utils.logError(`User sync type "${type}" not supported for luponmedia`); - } - } - } - } catch (e) { - utils.logError(e); - } - } - }); - } else { - utils.logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); - } - - hasSynced = true; - return allUserSyncs; - }, -}; - -function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { - bidRequest.startTime = new Date().getTime(); - - const bannerParams = utils.deepAccess(bidRequest, 'mediaTypes.banner'); - - let bannerSizes = []; - - if (bannerParams && bannerParams.sizes) { - const sizes = utils.parseSizesInput(bannerParams.sizes); - - // get banner sizes in form [{ w: , h: }, ...] - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - - bannerSizes = format; - } - - const data = { - id: bidRequest.transactionId, - test: config.getConfig('debug') ? 1 : 0, - source: { - tid: bidRequest.transactionId - }, - tmax: config.getConfig('timeout') || 1500, - imp: currentImps.concat([{ - id: bidRequest.bidId, - secure: 1, - ext: { - [bidRequest.bidder]: bidRequest.params - }, - banner: { - format: bannerSizes - } - }]), - ext: { - prebid: { - targeting: { - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false - } - } - }, - user: { - } - } - - const bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor')); - if (!isNaN(bidFloor)) { - data.imp[0].bidfloor = bidFloor; - } - appendSiteAppDevice(data, bidRequest, bidderRequest); - - const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); - if (digiTrust) { - utils.deepSetValue(data, 'user.ext.digitrust', digiTrust); - } - - if (bidderRequest.gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - - utils.deepSetValue(data, 'regs.ext.gdpr', gdprApplies); - utils.deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - utils.deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - // Set user uuid - utils.deepSetValue(data, 'user.id', utils.generateUUID()); - - // set crumbs - if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { - utils.deepSetValue(data, 'user.buyeruid', bidRequest.crumbs.pubcid); - } else { - utils.deepSetValue(data, 'user.buyeruid', utils.generateUUID()); - } - - if (bidRequest.userId && typeof bidRequest.userId === 'object' && - (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { - utils.deepSetValue(data, 'user.ext.eids', []); - - if (bidRequest.userId.tdid) { - data.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: bidRequest.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - - if (bidRequest.userId.pubcid) { - data.user.ext.eids.push({ - source: 'pubcommon', - uids: [{ - id: bidRequest.userId.pubcid, - }] - }); - } - - // support liveintent ID - if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { - data.user.ext.eids.push({ - source: 'liveintent.com', - uids: [{ - id: bidRequest.userId.lipb.lipbid - }] - }); - - data.user.ext.tpid = { - source: 'liveintent.com', - uid: bidRequest.userId.lipb.lipbid - }; - - if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { - utils.deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); - } - } - - // support identityLink (aka LiveRamp) - if (bidRequest.userId.idl_env) { - data.user.ext.eids.push({ - source: 'liveramp.com', - uids: [{ - id: bidRequest.userId.idl_env - }] - }); - } - } - - if (config.getConfig('coppa') === true) { - utils.deepSetValue(data, 'regs.coppa', 1); - } - - if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); - - if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { - const bidderData = { - bidders: [ bidderRequest.bidderCode ], - config: { - fpd: {} - } - }; - - if (!utils.isEmpty(siteData)) { - bidderData.config.fpd.site = siteData; - } - - if (!utils.isEmpty(userData)) { - bidderData.config.fpd.user = userData; - } - - utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); - } - - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); - } - - return data; -} - -export function hasValidSupplyChainParams(schain) { - let isValid = false; - const requiredFields = ['asi', 'sid', 'hp']; - if (!schain.nodes) return isValid; - isValid = schain.nodes.reduce((status, node) => { - if (!status) return status; - return requiredFields.every(field => node[field]); - }, true); - if (!isValid) utils.logError('LuponMedia: required schain params missing'); - return isValid; -} - -function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { - if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { - return null; - } - const propNames = DIGITRUST_PROP_NAMES[endpointName]; - - function getDigiTrustId() { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - - let digiTrustId = getDigiTrustId(); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } - - const digiTrustQueryParams = { - [propNames.id]: digiTrustId.id, - [propNames.keyv]: digiTrustId.keyv - }; - if (propNames.pref) { - digiTrustQueryParams[propNames.pref] = 0; - } - return digiTrustQueryParams; -} - -function _getPageUrl(bidRequest, bidderRequest) { - let pageUrl = config.getConfig('pageUrl'); - if (bidRequest.params.referrer) { - pageUrl = bidRequest.params.referrer; - } else if (!pageUrl) { - pageUrl = bidderRequest.refererInfo.referer; - } - return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; -} - -function appendSiteAppDevice(data, bidRequest, bidderRequest) { - if (!data) return; - - // ORTB specifies app OR site - if (typeof config.getConfig('app') === 'object') { - data.app = config.getConfig('app'); - } else { - data.site = { - page: _getPageUrl(bidRequest, bidderRequest) - } - } - if (typeof config.getConfig('device') === 'object') { - data.device = config.getConfig('device'); - } -} - -var hasSynced = false; - -export function resetUserSync() { - hasSynced = false; -} - -registerBidder(spec); diff --git a/modules/madvertiseBidAdapter.js b/modules/madvertiseBidAdapter.js index d0cafbfd6b8..219295f0d50 100644 --- a/modules/madvertiseBidAdapter.js +++ b/modules/madvertiseBidAdapter.js @@ -86,7 +86,11 @@ export const spec = { creativeId: responseObj.creativeId, netRevenue: responseObj.netRevenue, currency: responseObj.currency, - dealId: responseObj.dealId + dealId: responseObj.dealId, + meta: { + advertiserDomains: Array.isArray(responseObj.adomain) ? responseObj.adomain : [] + } + }; return [bid]; }, diff --git a/modules/madvertiseBidAdapter.md b/modules/madvertiseBidAdapter.md index 4576e955cbd..b57925f2dc1 100644 --- a/modules/madvertiseBidAdapter.md +++ b/modules/madvertiseBidAdapter.md @@ -30,7 +30,7 @@ support@madvertise.com for more information. { bidder: "madvertise", params: { - s: "/4543756/prebidadaptor/madvertiseHB" + zoneId: "/4543756/prebidadaptor/madvertiseHB" } } ] diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js new file mode 100644 index 00000000000..3431681ef2f --- /dev/null +++ b/modules/malltvAnalyticsAdapter.js @@ -0,0 +1,188 @@ +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 {getGlobal} from '../src/prebidGlobal.js' +import {logInfo, logError, deepClone} from '../src/utils.js' + +const analyticsType = 'endpoint' +export const ANALYTICS_VERSION = '1.0.0' +export const DEFAULT_SERVER = 'https://central.mall.tv/analytics' + +const { + EVENTS: { + AUCTION_END, + BID_TIMEOUT + } +} = CONSTANTS + +export const BIDDER_STATUS = { + BID: 1, + NO_BID: 2, + BID_WON: 3, + TIMEOUT: 4 +} + +export const getCpmInEur = function (bid) { + if (bid.currency !== 'EUR' && typeof bid.getCpmInNewCurrency === 'function') { + return bid.getCpmInNewCurrency('EUR'); + } + + return bid.cpm; +} + +const analyticsOptions = {} + +export const parseBidderCode = function (bid) { + let bidderCode = bid.bidderCode || bid.bidder + return bidderCode.toLowerCase() +} + +export const parseAdUnitCode = function (bidResponse) { + return bidResponse.adUnitCode.toLowerCase() +} + +export const malltvAnalyticsAdapter = Object.assign(adapter({DEFAULT_SERVER, analyticsType}), { + + cachedAuctions: {}, + + initConfig(config) { + /** + * Required option: propertyId + * + * Optional option: server + * @type {boolean} + */ + analyticsOptions.options = deepClone(config.options) + if (typeof config.options.propertyId !== 'string' || config.options.propertyId.length < 1) { + logError('"options.propertyId" is required.') + return false + } + + analyticsOptions.propertyId = config.options.propertyId + analyticsOptions.server = config.options.server || DEFAULT_SERVER + + return true + }, + track({eventType, args}) { + switch (eventType) { + case BID_TIMEOUT: + this.handleBidTimeout(args) + break + case AUCTION_END: + this.handleAuctionEnd(args) + break + } + }, + handleBidTimeout(timeoutBids) { + timeoutBids.forEach((bid) => { + const cachedAuction = this.getCachedAuction(bid.auctionId) + cachedAuction.timeoutBids.push(bid) + }) + }, + handleAuctionEnd(auctionEndArgs) { + const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId) + const highestCpmBids = getGlobal().getHighestCpmBids() + this.sendEventMessage('end', + this.createBidMessage(auctionEndArgs, highestCpmBids, cachedAuction.timeoutBids) + ) + }, + createBidMessage(auctionEndArgs, winningBids, timeoutBids) { + const {auctionId, timestamp, timeout, auctionEnd, adUnitCodes, bidsReceived, noBids} = auctionEndArgs + const message = this.createCommonMessage(auctionId) + + message.auctionElapsed = (auctionEnd - timestamp) + message.timeout = timeout + + adUnitCodes.forEach((adUnitCode) => { + message.adUnits[adUnitCode] = {} + }) + + // We handled noBids first because when currency conversion is enabled, a bid with a foreign currency + // will be set to NO_BID initially, and then set to BID after the currency rate json file is fully loaded. + // In this situation, the bid exists in both noBids and bids arrays. + noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID)) + + // This array may contain some timeout bids (responses come back after auction timeout) + bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID)) + + // We handle timeout after bids since it's possible that a bid has a response, but the response comes back + // after auction end. In this case, the bid exists in both bidsReceived and timeoutBids arrays. + timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT)) + + // mark the winning bids with prebidWon = true + winningBids.forEach(bid => { + const adUnitCode = parseAdUnitCode(bid) + const bidder = parseBidderCode(bid) + message.adUnits[adUnitCode][bidder].prebidWon = true + }) + return message + }, + createCommonMessage(auctionId) { + return { + analyticsVersion: ANALYTICS_VERSION, + auctionId: auctionId, + propertyId: analyticsOptions.propertyId, + referrer: window.location.href, + prebidVersion: '$prebid.version$', + adUnits: {}, + } + }, + addBidResponseToMessage(message, bid, status) { + const adUnitCode = parseAdUnitCode(bid) + message.adUnits[adUnitCode] = message.adUnits[adUnitCode] || {} + const bidder = parseBidderCode(bid) + const bidResponse = this.serializeBidResponse(bid, status) + message.adUnits[adUnitCode][bidder] = bidResponse + }, + serializeBidResponse(bid, status) { + const result = { + prebidWon: (status === BIDDER_STATUS.BID_WON), + isTimeout: (status === BIDDER_STATUS.TIMEOUT), + status: status, + } + if (status === BIDDER_STATUS.BID || status === BIDDER_STATUS.BID_WON) { + Object.assign(result, { + time: bid.timeToRespond, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm || bid.cpm, + cpmEur: getCpmInEur(bid), + originalCurrency: bid.originalCurrency || bid.currency, + vastUrl: bid.vastUrl + }) + } + return result + }, + sendEventMessage(endPoint, data) { + logInfo(`AJAX: ${endPoint}: ` + JSON.stringify(data)) + + ajax(`${analyticsOptions.server}/${endPoint}`, null, JSON.stringify(data), { + contentType: 'application/json' + }) + }, + getCachedAuction(auctionId) { + this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { + timeoutBids: [], + } + return this.cachedAuctions[auctionId] + }, + getAnalyticsOptions() { + return analyticsOptions + }, +}) + +// save the base class function +malltvAnalyticsAdapter.originEnableAnalytics = malltvAnalyticsAdapter.enableAnalytics + +// override enableAnalytics so we can get access to the config passed in from the page +malltvAnalyticsAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + malltvAnalyticsAdapter.originEnableAnalytics(config) // call the base class function + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: malltvAnalyticsAdapter, + code: 'malltvAnalytics' +}) diff --git a/modules/malltvAnalyticsAdapter.md b/modules/malltvAnalyticsAdapter.md new file mode 100644 index 00000000000..cc62d939694 --- /dev/null +++ b/modules/malltvAnalyticsAdapter.md @@ -0,0 +1,25 @@ +# Overview + +Module Name: Malltv Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: arditb@gjirafa.com + +# Description + +Analytics adapter for Malltv + +# Parameters + +``` +{ + provider: 'malltvAnalytics', + options: { + 'propertyId': 'YOUR_PROPERTY_ID', // Required + 'server': 'YOUR_ANALYTICS_SERVER' // Optional + } +} +``` + +PS. [Prebid currency module](http://prebid.org/dev-docs/modules/currency.html) is required, please make sure your prebid code contains currency module code. diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js new file mode 100644 index 00000000000..4e600135e0b --- /dev/null +++ b/modules/malltvBidAdapter.js @@ -0,0 +1,119 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'malltv'; +const ENDPOINT_URL = 'https://central.mall.tv/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +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.propertyId && 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 propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + let data = {}; + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } + if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } + if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } + + let adUnitId = bidRequest.adUnitCode; + let placementId = bidRequest.params.placementId; + let sizes = generateSizeParam(bidRequest.sizes); + + return { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: bidRequest.params.skipTime + }; + }); + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents, + data: data + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; + }, + /** + * 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) { + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: responses[i].BidId, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType, + meta: { + advertiserDomains: Array.isArray(responses[i].ADomain) ? responses[i].ADomain : [] + } + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +registerBidder(spec); diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md new file mode 100644 index 00000000000..e32eb54f90f --- /dev/null +++ b/modules/malltvBidAdapter.md @@ -0,0 +1,68 @@ +# Overview +Module Name: MallTV Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com + +# Description +MallTV Bidder Adapter for Prebid.js. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 300] + ] + } + }, + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + }, + { + code: 'test-div', + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + } +]; +``` diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index 960fbe27c73..61b7c31c8e4 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -1,7 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +export const storage = getStorageManager(); function inIframe() { try { @@ -247,6 +247,9 @@ export const spec = { width: ad.width, height: ad.height, ad: ad.html, + meta: { + advertiserDomains: ad.domains || [] + }, ttl: ad.ttl || serverResponse.body.ttl || 86400, creativeId: ad.view, netRevenue: true, diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 1ce2558b8de..bb1763ebb2e 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; function MarsmediaAdapter() { this.code = 'marsmedia'; @@ -16,7 +17,7 @@ function MarsmediaAdapter() { let SUPPORTED_VIDEO_API = [1, 2, 5]; let slotsToBids = {}; let that = this; - let version = '2.3'; + let version = '2.4'; this.isBidRequestValid = function (bid) { return !!(bid.params && bid.params.zoneId); @@ -53,6 +54,7 @@ function MarsmediaAdapter() { if (!(impObj.banner || impObj.video)) { continue; } + impObj.bidfloor = _getFloor(BRs[i]); impObj.ext = frameExt(BRs[i]); impList.push(impObj); } @@ -153,9 +155,31 @@ function MarsmediaAdapter() { } function frameExt(bid) { - return { - bidder: { - zoneId: bid.params['zoneId'] + if ((bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes)) { + 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 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); + + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: viewabilityAmountRounded + } + } else { + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: 'na' } } } @@ -180,12 +204,15 @@ function MarsmediaAdapter() { } }; if (BRs[0].schain) { - bid.source = { - 'ext': { - 'schain': BRs[0].schain - } - } + utils.deepSetValue(bid, 'source.ext.schain', BRs[0].schain); + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(bid, 'regs.ext.us_privacy', bidderRequest.uspConsent) } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(bid, 'regs.coppa', config.getConfig('coppa') & 1) + } + return bid; } @@ -241,12 +268,6 @@ function MarsmediaAdapter() { sendbeacon(bid, 20) }; - function sendbeacon(bid, type) { - const bidString = JSON.stringify(bid); - const encodedBuf = window.btoa(bidString); - utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); - } - this.interpretResponse = function (serverResponse) { let responses = serverResponse.body || []; let bids = []; @@ -295,6 +316,126 @@ function MarsmediaAdapter() { return bids; }; + + function sendbeacon(bid, type) { + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); + } + + /** + * Gets bidfloor + * @param {Object} bid + * @returns {Number} floor + */ + function _getFloor (bid) { + const curMediaType = bid.mediaTypes.video ? 'video' : 'banner'; + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = floorInfo.floor; + } + } + + return floor; + } + + function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); + } + + function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; + } + + function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } + } + + function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; + } + + function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + 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); + } + + return 0; + } + + 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; + } } export const spec = new MarsmediaAdapter(); diff --git a/modules/mass.js b/modules/mass.js new file mode 100644 index 00000000000..01135e7ddff --- /dev/null +++ b/modules/mass.js @@ -0,0 +1,184 @@ +/** + * This module adds MASS support to Prebid.js. + */ + +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +const defaultCfg = { + dealIdPattern: /^MASS/i +}; +let cfg; + +export let listenerAdded = false; +export let isEnabled = false; + +const matchedBids = {}; +let renderers; + +init(); +config.getConfig('mass', config => init(config.mass)); + +/** + * Module init. + */ +export function init(userCfg) { + cfg = Object.assign({}, defaultCfg, window.massConfig && window.massConfig.mass, userCfg); + + if (cfg.enabled === false) { + if (isEnabled) { + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + isEnabled = false; + } + } else { + if (!isEnabled) { + getHook('addBidResponse').before(addBidResponseHook); + isEnabled = true; + } + } + + if (isEnabled) { + updateRenderers(); + } +} + +/** + * Update the list of renderers based on current config. + */ +export function updateRenderers() { + renderers = []; + + // official MASS renderer: + if (cfg.dealIdPattern && cfg.renderUrl) { + renderers.push({ + match: isMassBid, + render: useDefaultRender(cfg.renderUrl, 'mass') + }); + } + + // add any custom renderer defined in the config: + (cfg.custom || []).forEach(renderer => { + if (!renderer.match && renderer.dealIdPattern) { + renderer.match = useDefaultMatch(renderer.dealIdPattern); + } + + if (!renderer.render && renderer.renderUrl && renderer.namespace) { + renderer.render = useDefaultRender(renderer.renderUrl, renderer.namespace); + } + + if (renderer.match && renderer.render) { + renderers.push(renderer); + } + }); + + return renderers; +} + +/** + * Before hook for 'addBidResponse'. + */ +export function addBidResponseHook(next, adUnitCode, bid) { + let renderer; + for (let i = 0; i < renderers.length; i++) { + if (renderers[i].match(bid)) { + renderer = renderers[i]; + break; + } + } + + if (renderer) { + const bidRequest = find(this.bidderRequest.bids, bidRequest => + bidRequest.bidId === bid.requestId + ); + + matchedBids[bid.requestId] = { + renderer, + payload: { + bidRequest, + bid, + adm: bid.ad + } + }; + + bid.ad = ' ``` +# Ad Unit and Setup: For Testing (Video Instream) -# Ad Unit and Setup: For Testing (Native) +```html + + + +``` +# Ad Unit and Setup: For Testing (Video Outstream) ```html +``` + +# Ad Unit and Setup: For Testing (Native) + +```html + + + + diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js new file mode 100644 index 00000000000..77889db5ff2 --- /dev/null +++ b/modules/medianetRtdProvider.js @@ -0,0 +1,112 @@ +import { submodule } from '../src/hook.js'; +import * as utils from '../src/utils.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 || !utils.isStr(customerId) || utils.isEmptyStr(customerId)) { + utils.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] || {}; + utils.mergeDeep(adUnit[OPEN_RTB_FIELD], openRtbProps[adUnit.code]); + utils.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 && utils.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); + utils.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 cb52c288caf..e442b01a115 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -1,7 +1,7 @@ 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 {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -13,7 +13,7 @@ const BIDDER_ENDPOINT_WINNING = 'winning'; export const spec = { code: BIDDER_CODE, aliases: ['msq'], // short code - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -59,7 +59,11 @@ export const spec = { } if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } - if (bidderRequest.userId) { payload.userId = bidderRequest.userId; } + if (bidderRequest.userId) { + payload.userId = bidderRequest.userId; + } else if (bidderRequest.hasOwnProperty('bids') && typeof bidderRequest.bids == 'object' && bidderRequest.bids.length > 0 && bidderRequest.bids[0].hasOwnProperty('userId')) { + payload.userId = bidderRequest.bids[0].userId; + } }; if (test) { payload.debug = true; } const payloadString = JSON.stringify(payload); @@ -97,8 +101,22 @@ 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'; + } else if ('video' in value) { + if ('url' in value['video']) { bidResponse['vastUrl'] = value['video']['url'] } + if ('xml' in value['video']) { bidResponse['vastXml'] = value['video']['xml'] } + bidResponse['mediaType'] = 'video'; + } if (value.hasOwnProperty('deal_id')) { bidResponse['dealId'] = value['deal_id']; } bidResponses.push(bidResponse); }); @@ -116,7 +134,8 @@ export const spec = { getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { let params = ''; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - if (serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { + 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}`; } @@ -136,16 +155,17 @@ export const spec = { // fires a pixel to confirm a winning bid let params = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond'] + let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId'] 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 new file mode 100644 index 00000000000..4ab29ec6f68 --- /dev/null +++ b/modules/merkleIdSystem.js @@ -0,0 +1,179 @@ +/** + * This module adds merkleId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/merkleIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; + +const MODULE_NAME = 'merkleId'; +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') { + session = configParams.sv_session; + } else { + session = storage.getCookie(SESSION_COOKIE_NAME); + } + return session; +} + +function setCookie(name, value, expires) { + let expTime = new Date(); + expTime.setTime(expTime.getTime() + expires * 1000 * 60); + storage.setCookie(name, value, expTime.toUTCString()); +} + +function setSession(storage, response) { + utils.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}`; + if (session) { + url = `${url}&sv_session=${session}`; + } + utils.logInfo('Merkle url :' + url); + return url; +} + +function generateId(configParams, configStorage) { + const url = constructUrl(configParams); + + const resp = function (callback) { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + setSession(configStorage, responseObj) + utils.logInfo('Merkle responseObj ' + JSON.stringify(responseObj)); + } catch (error) { + utils.logError(error); + } + } + + const date = new Date().toUTCString(); + responseObj.date = date; + utils.logInfo('Merkle responseObj with date ' + JSON.stringify(responseObj)); + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: merkleId fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); + }; + return resp; +} + +/** @type {Submodule} */ +export const merkleIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{merkleId:string}} + */ + 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; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData) { + utils.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'); + return; + } + + if (typeof configParams.sv_cid !== 'string') { + utils.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'); + return; + } + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { + utils.logError('User ID - merkleId submodule does not currently handle consent strings'); + return; + } + + 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) { + utils.logInfo('User ID - merkleId stored id ' + storedId); + const configParams = (config && config.params) || {}; + + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { + utils.logError('User ID - merkleId submodule does not currently handle consent strings'); + return; + } + + 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; + utils.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) { + utils.logInfo('User ID - merkleId needs refreshing id'); + const resp = generateId(configParams, configStorage) + return {callback: resp}; + } + } + utils.logInfo('User ID - merkleId not refreshed'); + return {id: storedId}; + } + +}; + +submodule('userId', merkleIdSubmodule); diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 957b9a1d703..3b45c1e69a5 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -4,9 +4,10 @@ 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]: '; @@ -62,7 +63,7 @@ utils._each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] utils._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]*$/, @@ -130,7 +131,7 @@ export const spec = { if (utils.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 => { @@ -141,9 +142,12 @@ export const spec = { 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) { @@ -171,7 +175,7 @@ export const spec = { let request = { id: utils.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) { @@ -292,6 +296,7 @@ function prebidBid(serverBid, cur) { nurl: serverBid.nurl || '', burl: serverBid.burl || '', isBurl: utils.isStr(serverBid.burl) && serverBid.burl.length > 0, + meta: { advertiserDomains: (utils.isArray(serverBid.adomain) && serverBid.adomain.length > 0 ? serverBid.adomain : []) }, }; setMediaType(serverBid, bid); switch (bid.mediaType) { @@ -375,6 +380,10 @@ function createBannerRequest(bid) { if (format.length) { r.format = format } + const pos = utils.deepAccess(bid, 'mediaTypes.banner.pos') || 0 + if (pos) { + r.pos = pos + } return r } @@ -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 = utils.getBidIdParameter('bidfloor', bid.params) || utils.getBidIdParameter('bidFloor', bid.params) || 0; + const reqCur = cur + + if (!bidFloor && utils.isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: '*', + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floorObj) && utils.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..c78a66aecee 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -1,5 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; const BIDDER_CODE = 'microad'; @@ -81,6 +82,11 @@ export const spec = { } } + const idlEnv = utils.deepAccess(bid, 'userId.idl_env') + if (!utils.isEmpty(idlEnv) && utils.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.md b/modules/missenaBidAdapter.md new file mode 100644 index 00000000000..d5fcacf04ab --- /dev/null +++ b/modules/missenaBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +``` +Module Name: Missena Bid Adapter +Module Type: Bidder Adapter +Maintainer: jney@missena.com +``` + +## Introduction + +Connects to Missena for bids. + +**Note:** this adapter doesn't support SafeFrame. + +Useful resources: + +- [README](../README.md#Build) +- [https://docs.prebid.org/dev-docs/bidder-adaptor.html](https://docs.prebid.org/dev-docs/bidder-adaptor.html) + +## Develop + +Setup the missena adapter in `integrationExamples/gpt/userId_example.html`. + +For example: + +```js +const AD_UNIT_CODE = "test-div"; +const PUBLISHER_MISSENA_TOKEN = "PA-34745704"; + +var adUnits = [ + { + code: AD_UNIT_CODE, + mediaTypes: { + banner: { + sizes: [1, 1], + }, + }, + bids: [ + { + bidder: "missena", + params: { + apiKey: PUBLISHER_MISSENA_TOKEN, + }, + }, + ], + }, +]; +``` + +Then start the demo app: + +```shell +gulp serve-fast --modules=missenaBidAdapter +``` + +And open [http://localhost:9999/integrationExamples/gpt/userId_example.html](http://localhost:9999/integrationExamples/gpt/userId_example.html) + +## Test + +```shell +gulp test --file test/spec/modules/missenaBidAdapter_spec.js +``` + +Add the `--watch` option to re-run unit tests whenever the source code changes. + +## Build + +```shell +gulp build --modules=missenaBidAdapter +``` 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.md b/modules/mobfoxpbBidAdapter.md new file mode 100644 index 00000000000..6eb549919d7 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: mobfox Bidder Adapter +Module Type: mobfox Bidder Adapter +Maintainer: platform@mobfox.com +``` + +# Description + +Module that connects to mobfox demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + } + ]; +``` \ No newline at end of file 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 new file mode 100644 index 00000000000..dd4999b2dca --- /dev/null +++ b/modules/multibid/index.js @@ -0,0 +1,234 @@ +/** + * This module adds Multibid support to prebid.js + * @module modules/multibid + */ + +import {config} from '../../src/config.js'; +import {setupBeforeHookFnOnce, getHook} from '../../src/hook.js'; +import * as utils from '../../src/utils.js'; +import events from '../../src/events.js'; +import CONSTANTS from '../../src/constants.json'; +import {addBidderRequests} from '../../src/auction.js'; +import {getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm} from '../../src/targeting.js'; + +const MODULE_NAME = 'multibid'; +let hasMultibid = false; +let multiConfig = {}; +let multibidUnits = {}; + +// Storing this globally on init for easy reference to configuration +config.getConfig(MODULE_NAME, conf => { + if (!Array.isArray(conf.multibid) || !conf.multibid.length || !validateMultibid(conf.multibid)) return; + + resetMultiConfig(); + hasMultibid = true; + + conf.multibid.forEach(entry => { + if (entry.bidder) { + multiConfig[entry.bidder] = { + maxbids: entry.maxBids, + prefix: entry.targetBiddercodePrefix + } + } else { + entry.bidders.forEach(key => { + multiConfig[key] = { + maxbids: entry.maxBids, + prefix: entry.targetBiddercodePrefix + } + }); + } + }); +}); + +/** + * @summary validates multibid configuration entries + * @param {Object[]} multibid - example [{bidder: 'bidderA', maxbids: 2, prefix: 'bidA'}, {bidder: 'bidderB', maxbids: 2}] + * @return {Boolean} +*/ +export function validateMultibid(conf) { + let check = true; + 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.'); + check = false; + return false; + } + + return true; + }).map(entry => { + // Check if entry.maxbids is not defined, not typeof number, or less than 1, set maxbids to 1 and reset configuration + // Check if entry.maxbids is greater than 9, set maxbids to 9 and reset configuration + if (typeof entry.maxBids !== 'number' || entry.maxBids < 1 || entry.maxBids > 9) { + entry.maxBids = (typeof entry.maxBids !== 'number' || entry.maxBids < 1) ? 1 : 9; + check = false; + } + + return entry; + }); + + if (!check) config.setConfig({multibid: duplicate}); + + return check; +} + +/** + * @summary addBidderRequests before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {Object[]} array containing copy of each bidderRequest object +*/ +export function adjustBidderRequestsHook(fn, bidderRequests) { + bidderRequests.map(bidRequest => { + // Loop through bidderRequests and check if bidderCode exists in multiconfig + // If true, add bidderRequest.bidLimit to bidder request + if (multiConfig[bidRequest.bidderCode]) { + bidRequest.bidLimit = multiConfig[bidRequest.bidderCode].maxbids + } + return bidRequest; + }) + + fn.call(this, bidderRequests); +} + +/** + * @summary addBidResponse before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {String} ad unit code for bid + * @param {Object} bid object +*/ +export function addBidResponseHook(fn, adUnitCode, bid) { + let floor = utils.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') { + 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}`)) { + // 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(); + multibidUnits[adUnitCode][bid.bidderCode].ads.push(bid); + + let length = multibidUnits[adUnitCode][bid.bidderCode].ads.length; + + if (multiConfig[bid.bidderCode].prefix) bid.targetingBidder = multiConfig[bid.bidderCode].prefix + length; + if (length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true; + + 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.')); + } + } else { + if (utils.deepAccess(bid, 'floorData.floorValue')) utils.deepSetValue(multibidUnits, `${adUnitCode}.${bid.bidderCode}`, {floor: utils.deepAccess(bid, 'floorData.floorValue')}); + + utils.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); + } + } else { + fn.call(this, adUnitCode, bid); + } +} + +/** +* A descending sort function that will sort the list of objects based on the following: +* - bids without dynamic aliases are sorted before bids with dynamic aliases +*/ +export function sortByMultibid(a, b) { + if (a.bidder !== a.bidderCode && b.bidder === b.bidderCode) { + return 1; + } + + if (a.bidder === a.bidderCode && b.bidder !== b.bidderCode) { + return -1; + } + + return 0; +} + +/** + * @summary getHighestCpmBidsFromBidPool before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {Object[]} array of objects containing all bids from bid pool + * @param {Function} function to reduce to only highest cpm value for each bidderCode + * @param {Number} adUnit bidder targeting limit, default set to 0 + * @param {Boolean} default set to false, this hook modifies targeting and sets to true +*/ +export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + if (!config.getConfig('multibid')) resetMultiConfig(); + if (hasMultibid) { + const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); + let modifiedBids = []; + let buckets = utils.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 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) => { + if (bidA.originalBidder && bidA.originalBidder !== bidA.bidderCode) bidA.bidderCode = bidA.originalBidder; + if (bidA.originalBidder && bidB.originalBidder !== bidB.bidderCode) bidB.bidderCode = bidB.originalBidder; + 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) { + bid.bidderCode = multiConfig[bid.bidderCode].prefix + (index + 1); + } + + return bid + }) + })); + // Get adjustedBids by bidderCode and reduce using highestCpmCallback + let bidsByBidderCode = utils.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) { + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); + bucketBids.sort(sortByMultibid); + modifiedBids.push(...bucketBids.slice(0, adUnitBidLimit)); + } else { + modifiedBids.push(...bucketBids); + } + + return [].concat.apply([], modifiedBids); + }, [])); + + fn.call(this, bids, highestCpmCallback, adUnitBidLimit, true); + } else { + fn.call(this, bidsReceived, highestCpmCallback, adUnitBidLimit); + } +} + +/** +* Resets globally stored multibid configuration +*/ +export const resetMultiConfig = () => { hasMultibid = false; multiConfig = {}; }; + +/** +* Resets globally stored multibid ad unit bids +*/ +export const resetMultibidUnits = () => multibidUnits = {}; + +/** +* Set up hooks on init +*/ +function init() { + events.on(CONSTANTS.EVENTS.AUCTION_INIT, resetMultibidUnits); + setupBeforeHookFnOnce(addBidderRequests, adjustBidderRequestsHook); + getHook('addBidResponse').before(addBidResponseHook, 3); + setupBeforeHookFnOnce(getHighestCpmBidsFromBidPool, targetBidPoolHook); +} + +init(); diff --git a/modules/multibid/index.md b/modules/multibid/index.md new file mode 100644 index 00000000000..a431d9b7960 --- /dev/null +++ b/modules/multibid/index.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: multibid + +Purpose: To expand the number of key value pairs going to the ad server in the normal Prebid way by establishing the concept of a "dynamic alias" -- a bidder code that exists only on the response, not in the adunit. + + +# Description +Allowing a single bidder to multi-bid into an auction has several use cases: + +1. allows a bidder to provide both outstream and banner +2. supports the video VAST fallback scenario +3. allows one bid to be blocked in the ad server and the second one still considered +4. add extra high-value bids to the cache for future refreshes + + +# Example of using config +``` + pbjs.setConfig({ + multibid: [{ + bidder: "bidderA", + maxBids: 3, + targetBiddercodePrefix: "bidA" + },{ + bidder: "bidderB", + maxBids: 3, + targetBiddercodePrefix: "bidB" + },{ + bidder: "bidderC", + maxBids: 3 + },{ + bidders: ["bidderD", "bidderE"], + maxBids: 2 + }] + }); +``` + +# Please Note: +- + diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js new file mode 100644 index 00000000000..b2381836d5d --- /dev/null +++ b/modules/mwOpenLinkIdSystem.js @@ -0,0 +1,142 @@ +/** + * This module adds MediaWallah OpenLink to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/mwOpenLinkIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const openLinkID = { + name: 'mwol', + cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year +} + +const storage = getStorageManager(); + +function getExpirationDate() { + return (new Date(utils.timestamp() + openLinkID.cookie_expiration)).toGMTString(); +} + +function isValidConfig(configParams) { + if (!configParams) { + utils.logError('User ID - mwOlId submodule requires configParams'); + return false; + } + if (!configParams.accountId) { + utils.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'); + return false; + } + return true; +} + +function deserializeMwOlId(mwOlIdStr) { + const mwOlId = {}; + const mwOlIdArr = mwOlIdStr.split(','); + + mwOlIdArr.forEach(function(value) { + const pair = value.split(':'); + // unpack a value of 1 as true + mwOlId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + }); + + return mwOlId; +} + +function serializeMwOlId(mwOlId) { + let components = []; + + if (mwOlId.eid) { + components.push('eid:' + mwOlId.eid); + } + if (mwOlId.ibaOptout) { + components.push('ibaOptout:1'); + } + if (mwOlId.ccpaOptout) { + components.push('ccpaOptout:1'); + } + + return components.join(','); +} + +function readCookie(name) { + if (!name) name = openLinkID.name; + const mwOlIdStr = storage.getCookie(name); + if (mwOlIdStr) { + return deserializeMwOlId(decodeURIComponent(mwOlIdStr)); + } + return null; +} + +function writeCookie(mwOlId) { + if (mwOlId) { + const mwOlIdStr = encodeURIComponent(serializeMwOlId(mwOlId)); + storage.setCookie(openLinkID.name, mwOlIdStr, getExpirationDate(), 'lax'); + } +} + +function register(configParams, olid) { + const { accountId, partnerId, uid } = configParams; + const url = 'https://ol.mediawallahscript.com/?account_id=' + accountId + + '&partner_id=' + partnerId + + '&uid=' + uid + + '&olid=' + olid + + '&cb=' + Math.random() + ; + ajax(url); +} + +function setID(configParams) { + if (!isValidConfig(configParams)) return undefined; + const mwOlId = readCookie(); + const newMwOlId = mwOlId ? utils.deepClone(mwOlId) : {eid: utils.generateUUID()}; + writeCookie(newMwOlId); + register(configParams, newMwOlId.eid); + return { + id: newMwOlId + }; +}; + +/* End MW */ + +export { writeCookie }; + +/** @type {Submodule} */ +export const mwOpenLinkIdSubModule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'mwOpenLinkId', + /** + * decode the stored id value for passing to bid requests + * @function + * @param {MwOlId} mwOlId + * @return {(Object|undefined} + */ + decode(mwOlId) { + const id = mwOlId && utils.isPlainObject(mwOlId) ? mwOlId.eid : undefined; + return id ? { 'mwOpenLinkId': id } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [submoduleParams] + * @returns {id:MwOlId | undefined} + */ + getId(submoduleConfig) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; + if (!isValidConfig(submoduleConfigParams)) return undefined; + return setID(submoduleConfigParams); + } +}; + +submodule('userId', mwOpenLinkIdSubModule); diff --git a/modules/mwOpenLinkIdSystem.md b/modules/mwOpenLinkIdSystem.md new file mode 100644 index 00000000000..f55913f2983 --- /dev/null +++ b/modules/mwOpenLinkIdSystem.md @@ -0,0 +1,43 @@ +## MediaWallah openLink User ID Submodule + +OpenLink ID User ID Module generates a UUID that can be utilized to improve user matching. This module enables timely synchronization which handles MediaWallah optout. You must have a pre-existing relationship with MediaWallah prior to integrating. + +### Building Prebid with openLink Id Support +Your Prebid build must include the modules for both **userId** and **mwOpenLinkId** 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,mwOpenLinkIdSystem + +##$ MediaWallah openLink ID Example Configuration + +When the module is included, it's automatically enabled and saves an id to both cookie with an expiration time of 1 year. + +### Prebid Params + +Individual params may be set for the MediaWallah openLink User ID Submodule. At least accountId and partnerId must be set in the params. + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'mwOpenLinkId', + params: { + accountId: '1000', + partnerId: '1001', + uid: 'u-123xyz' + } + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the MediaWallah OpenLink ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `'mwOpenLinkId'` | +| params | Required | Object | Details for mwOLID syncing. | | +| params.accountId | Required | String | The MediaWallah assigned Account Id | `1000` | +| params.partnerId | Required | String | The MediaWallah assign Partner Id | `1001` | +| params.uid | Optional | String | Your unique Id for the user or browser. Used for matching | `u-123xyz` | \ No newline at end of file diff --git a/modules/mytargetBidAdapter.js b/modules/mytargetBidAdapter.js deleted file mode 100644 index bcf8e7ad17f..00000000000 --- a/modules/mytargetBidAdapter.js +++ /dev/null @@ -1,113 +0,0 @@ -import * as utils 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 DEFAULT_CURRENCY = 'RUB'; -const DEFAULT_TTL = 180; - -function buildPlacement(bidRequest) { - let { bidId, params } = bidRequest; - let { placementId, position, response, bidfloor } = params; - let placement = { - placementId, - id: bidId, - position: position || 0, - response: response || 0 - }; - - if (typeof bidfloor !== 'undefined') { - placement.bidfloor = bidfloor; - } - - return placement; -} - -function getSiteName(referrer) { - let sitename = config.getConfig('mytarget.sitename'); - - if (!sitename) { - sitename = utils.parseUrl(referrer).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); -} - -export const spec = { - code: BIDDER_CODE, - - isBidRequestValid: function(bid) { - return !!bid.params.placementId; - }, - - buildRequests: function(validBidRequests, bidderRequest) { - let referrer = ''; - - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; - } - - const payload = { - places: utils._map(validBidRequests, buildPlacement), - site: { - sitename: getSiteName(referrer), - page: referrer - }, - settings: { - currency: getCurrency(), - windowSize: { - width: window.screen.width, - height: window.screen.height - } - } - }; - - return { - method: 'POST', - url: BIDDER_URL, - data: payload, - }; - }, - - interpretResponse: function(serverResponse, bidRequest) { - let { body } = serverResponse; - - if (body.bids) { - return utils._map(body.bids, (bid) => { - let bidResponse = { - requestId: bid.id, - cpm: bid.price, - width: bid.size.width, - height: bid.size.height, - ttl: bid.ttl || DEFAULT_TTL, - currency: bid.currency || DEFAULT_CURRENCY, - creativeId: bid.creativeId || generateRandomId(), - netRevenue: true - } - - if (bid.adm) { - bidResponse.ad = bid.adm; - } else { - bidResponse.adUrl = bid.displayUrl; - } - - return bidResponse; - }); - } - - return []; - } -} - -registerBidder(spec); 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 new file mode 100644 index 00000000000..880ff4b6a8e --- /dev/null +++ b/modules/nativoBidAdapter.js @@ -0,0 +1,329 @@ +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' + +const BIDDER_CODE = 'nativo' +const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid' + +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 + +export const spec = { + code: BIDDER_CODE, + aliases: ['ntv'], // short code + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * 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.placementId + }, + + /** + * Called when the page asks Prebid.js for bids + * Make a server request from the list of BidRequests + * + * @param {Array} validBidRequests - An array of bidRequest objects, one for each AdUnit that your module is involved in. This array has been processed for special features like sizeConfig, so it’s the list that you should be looping through + * @param {Object} bidderRequest - The master bidRequest object. This object is useful because it carries a couple of bid parameters that are global to all the bids. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const placementIds = [] + 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] = { + 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]++ : 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 (bidderRequest.gdprConsent) { + // Put on the beginning of the qs param array + params.unshift({ + key: 'ntv_gdpr_consent', + value: bidderRequest.gdprConsent.consentString, + }) + } + + if (bidderRequest.uspConsent) { + // Put on the beginning of the qs param array + params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) + } + + let serverRequest = { + method: 'GET', + url: BIDDER_ENDPOINT + arrayToQS(params), + } + + return serverRequest + }, + + /** + * Will be called when the browser has received the response from your server. + * The function will parse the response and create a bidResponse object containing one or more bids. + * The adapter should indicate no valid bids by returning an empty array. + * + * @param {Object} response - Data returned from the bidding server request endpoint + * @param {Object} request - The request object used to call the server request endpoint + * @return {Array} An array of bids which were nested inside the server. + */ + interpretResponse: function (response, request) { + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) return [] + + try { + const body = + typeof response.body === 'string' + ? JSON.parse(response.body) + : response.body + + const bidResponses = [] + const seatbids = body.seatbid + + // Step through and grab pertinent data + let bidResponse, adUnit + seatbids.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + adUnit = this.getRequestId(body.id, bid.impid) + bidResponse = { + requestId: adUnit.bidId, + cpm: bid.price, + currency: body.cur, + width: bid.w || adUnit.size[0], + height: bid.h || adUnit.size[1], + creativeId: bid.crid, + dealId: bid.id, + netRevenue: true, + ttl: bid.ttl || TIME_TO_LIVE, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain, + }, + } + + bidResponses.push(bidResponse) + }) + }) + + // Don't need the map anymore as it was unique for one request/response + delete bidRequestMap[body.id] + + return bidResponses + } catch (error) { + // If there is an error, return [] + return [] + } + }, + + /** + * All user ID sync activity should be done using the getUserSyncs callback of the BaseAdapter model. + * Given an array of all the responses from the server, getUserSyncs is used to determine which user syncs should occur. + * The order of syncs in the serverResponses array matters. The most important ones should come first, since publishers may limit how many are dropped on their page. + * @param {Object} syncOptions - Which user syncs are allowed? + * @param {Array} serverResponses - Array of server's responses + * @param {Object} gdprConsent - GDPR consent data + * @param {Object} uspConsent - USP consent data + * @return {Array} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + // Generate consent qs string + let params = '' + // GDPR + if (gdprConsent) { + params = appendQSParamString( + params, + 'gdpr', + gdprConsent.gdprApplies ? 1 : 0 + ) + params = appendQSParamString( + params, + 'gdpr_consent', + encodeURIComponent(gdprConsent.consentString || '') + ) + } + // CCPA + if (uspConsent) { + params = appendQSParamString( + params, + 'us_privacy', + encodeURIComponent(uspConsent.uspConsent) + ) + } + + // Get sync urls from the respnse and inject cinbsent params + const types = { + iframe: syncOptions.iframeEnabled, + image: syncOptions.pixelEnabled, + } + const syncs = [] + + let body + serverResponses.forEach((response) => { + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) { + return syncs + } + + body = + typeof response.body === 'string' + ? JSON.parse(response.body) + : response.body + + // Make sure we have valid content + if (!body || !body.seatbid || body.seatbid.length === 0) return + + body.seatbid.forEach((seatbid) => { + // Grab the syncs for each seatbid + seatbid.syncUrls.forEach((sync) => { + if (types[sync.type]) { + if (sync.url.trim() !== '') { + syncs.push({ + type: sync.type, + url: sync.url.replace('{GDPR_params}', params), + }) + } + } + }) + }) + }) + + return syncs + }, + + /** + * Will be called when an adpater timed out for an auction. + * Adapter can fire a ajax or pixel call to register a timeout at thier end. + * @param {Object} timeoutData - Timeout specific data + */ + onTimeout: function (timeoutData) {}, + + /** + * Will be called when a bid from the adapter won the auction. + * @param {Object} bid - The bid that won the auction + */ + onBidWon: function (bid) {}, + + /** + * Will be called when the adserver targeting has been set for a bid from the adapter. + * @param {Object} bidder - The bid of which the targeting has been set + */ + onSetTargeting: function (bid) {}, + + /** + * 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 + * @returns {String} - The bidId value associated with the corresponding placementId + */ + getRequestId: function (bidderRequestId, placementId) { + return ( + bidRequestMap[bidderRequestId] && + bidRequestMap[bidderRequestId][placementId] + ) + }, +} +registerBidder(spec) + +// Utils +/** + * Append QS param to existing string + * @param {String} str - String to append to + * @param {String} key - Key to append + * @param {String} value - Value to append + * @returns + */ +function appendQSParamString(str, key, value) { + return str + `${str.length ? '&' : ''}${key}=${value}` +} + +/** + * Convert an object to query string parameters + * @param {Object} obj - Object to convert + * @returns + */ +function arrayToQS(arr) { + return ( + '?' + + arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') + ) +} + +/** + * Get the largest size array + * @param {Array} sizes - Array of size arrays + * @returns Size array with the largest area + */ +function getLargestSize(sizes, method = area) { + if (!sizes || sizes.length === 0) return [] + if (sizes.length === 1) return sizes[0] + + return sizes.reduce((prev, current) => { + if (method(current) > method(prev)) { + return current + } else { + return prev + } + }) +} + +/** + * Calculate the area + * @param {Array} size - [width, height] + * @returns The calculated area + */ +const area = (size) => size[0] * size[1] diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md new file mode 100644 index 00000000000..ec0980aae50 --- /dev/null +++ b/modules/nativoBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Nativo Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebiddev@nativo.com +``` + +# Description + +Module that connects to Nativo's demand sources + +# Dev + +gulp serve --modules=nativoBidAdapter + +# Test Parameters + +``` +var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + 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..c37530b4151 --- /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 * as utils 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 ? utils.isStr(value) ? value : utils.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) { + utils.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/netIdSystem.js b/modules/netIdSystem.js index cfe78d9f488..90c8735c993 100644 --- a/modules/netIdSystem.js +++ b/modules/netIdSystem.js @@ -26,12 +26,12 @@ export const netIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { /* currently not possible */ return {}; } 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..2101bb34e2b 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,10 +1,9 @@ -import * as utils 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 BIDDER_CODE = 'nextMillennium'; -const HOST = 'https://brainlyads.com'; -const CURRENCY = 'USD'; +const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; const TIME_TO_LIVE = 360; export const spec = { @@ -13,24 +12,43 @@ export const spec = { isBidRequestValid: function(bid) { return !!( - bid.params.placement_id && utils.isNumber(bid.params.placement_id) + bid.params.placement_id && utils.isStr(bid.params.placement_id) ); }, - buildRequests: function(validBidRequests) { - let requests = []; + buildRequests: function(validBidRequests, bidderRequest) { + const requests = []; utils._each(validBidRequests, function(bid) { + const postBody = { + 'id': bid.auctionId, + 'ext': { + 'prebid': { + 'storedrequest': { + 'id': utils.getBidIdParameter('placement_id', bid.params) + } + } + } + } + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + postBody.gdprApplies = !!gdprConsent.gdprApplies; + } + if (typeof gdprConsent.consentString !== 'undefined') { + postBody.consentString = gdprConsent.consentString; + } + } + 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 +57,30 @@ 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) { + utils._each(response.seatbid, (resp) => { + utils._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 []; - } - }, - - getUserSyncs: function(syncOptions) { - const syncs = [] - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: HOST + '/hb/s2s/matching' }); - } + }); - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: HOST + '/hb/s2s/matching' - }); - } - return syncs; + return bidResponses; } }; + 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 deleted file mode 100644 index 02ebcd3f87a..00000000000 --- a/modules/nextrollBidAdapter.js +++ /dev/null @@ -1,345 +0,0 @@ -import * as utils 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 = 'nextroll'; -const BIDDER_ENDPOINT = 'https://d.adroll.com/bid/prebid/'; -const ADAPTER_VERSION = 5; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], - - /** - * 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 (bidRequest) { - return bidRequest !== undefined && !!bidRequest.params && !!bidRequest.bidId; - }, - - /** - * 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 topLocation = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); - - return validBidRequests.map((bidRequest) => { - return { - method: 'POST', - options: { - withCredentials: true, - }, - url: BIDDER_ENDPOINT, - data: { - id: bidRequest.bidId, - imp: { - id: bidRequest.bidId, - bidfloor: utils.getBidIdParameter('bidfloor', bidRequest.params), - banner: _getBanner(bidRequest), - native: _getNative(utils.deepAccess(bidRequest, 'mediaTypes.native')), - ext: { - zone: { - id: utils.getBidIdParameter('zoneId', bidRequest.params) - }, - nextroll: { - adapter_version: ADAPTER_VERSION - } - } - }, - - user: _getUser(validBidRequests), - site: _getSite(bidRequest, topLocation), - seller: _getSeller(bidRequest), - device: _getDevice(bidRequest), - regs: _getRegs(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) { - if (!serverResponse.body) { - return []; - } else { - let response = serverResponse.body - let bids = response.seatbid.reduce((acc, seatbid) => acc.concat(seatbid.bid), []); - return bids.map((bid) => _buildResponse(response, bid)); - } - } -} - -function _getBanner(bidRequest) { - let sizes = _getSizes(bidRequest); - if (sizes === undefined) return undefined; - return {format: sizes}; -} - -function _getNative(mediaTypeNative) { - if (mediaTypeNative === undefined) return undefined; - let assets = _getNativeAssets(mediaTypeNative); - if (assets === undefined || assets.length == 0) return undefined; - return { - request: { - native: { - assets: assets - } - } - }; -} - -/* - id: Unique numeric id for the asset - kind: OpenRTB kind of asset. Supported: title, img and data. - key: Name of property that comes in the mediaType.native object. - type: OpenRTB type for that spefic kind of asset. - required: Overrides the asset required field configured, only overrides when is true. -*/ -const NATIVE_ASSET_MAP = [ - {id: 1, kind: 'title', key: 'title', required: true}, - {id: 2, kind: 'img', key: 'image', type: 3, required: true}, - {id: 3, kind: 'img', key: 'icon', type: 1}, - {id: 4, kind: 'img', key: 'logo', type: 2}, - {id: 5, kind: 'data', key: 'sponsoredBy', type: 1}, - {id: 6, kind: 'data', key: 'body', type: 2} -]; - -const ASSET_KIND_MAP = { - title: _getTitleAsset, - img: _getImageAsset, - data: _getDataAsset, -}; - -function _getAsset(mediaTypeNative, assetMap) { - const asset = mediaTypeNative[assetMap.key]; - if (asset === undefined) return undefined; - const assetFunc = ASSET_KIND_MAP[assetMap.kind]; - return { - id: assetMap.id, - required: (assetMap.required || !!asset.required) ? 1 : 0, - [assetMap.kind]: assetFunc(asset, assetMap) - }; -} - -function _getTitleAsset(title, _assetMap) { - return {len: title.len || 0}; -} - -function _getMinAspectRatio(aspectRatio, property) { - if (!utils.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; - - return 1; -} - -function _getImageAsset(image, assetMap) { - const sizes = image.sizes; - const aspectRatio = image.aspect_ratios ? image.aspect_ratios[0] : undefined; - - return { - type: assetMap.type, - w: (sizes ? sizes[0] : undefined), - h: (sizes ? sizes[1] : undefined), - wmin: _getMinAspectRatio(aspectRatio, 'width'), - hmin: _getMinAspectRatio(aspectRatio, 'height'), - }; -} - -function _getDataAsset(data, assetMap) { - return { - type: assetMap.type, - len: data.len || 0 - }; -} - -function _getNativeAssets(mediaTypeNative) { - return NATIVE_ASSET_MAP - .map(assetMap => _getAsset(mediaTypeNative, assetMap)) - .filter(asset => asset !== undefined); -} - -function _getUser(requests) { - const id = utils.deepAccess(requests, '0.userId.nextroll'); - if (id === undefined) { - return; - } - - return { - ext: { - eid: [{ - 'source': 'nextroll', - id - }] - } - }; -} - -function _buildResponse(bidResponse, bid) { - let response = { - requestId: bidResponse.id, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - dealId: bidResponse.dealId, - currency: 'USD', - netRevenue: true, - ttl: 300 - }; - if (utils.isStr(bid.adm)) { - response.mediaType = BANNER; - response.ad = utils.replaceAuctionPrice(bid.adm, bid.price); - } else { - response.mediaType = NATIVE; - response.native = _getNativeResponse(bid.adm, bid.price); - } - return response; -} - -const privacyLink = 'https://info.evidon.com/pub_info/573'; -const privacyIcon = 'https://c.betrad.com/pub/icon1.png'; - -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)), - privacyLink: privacyLink, - privacyIcon: privacyIcon - }; - return adm.assets.reduce((accResponse, asset) => { - const assetMaps = NATIVE_ASSET_MAP.filter(assetMap => assetMap.id === asset.id && asset[assetMap.kind] !== undefined); - if (assetMaps.length === 0) return accResponse; - const assetMap = assetMaps[0]; - accResponse[assetMap.key] = _getAssetResponse(asset, assetMap); - return accResponse; - }, baseResponse); -} - -function _getAssetResponse(asset, assetMap) { - switch (assetMap.kind) { - case 'title': - return asset.title.text; - - case 'img': - return { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - - case 'data': - return asset.data.value; - } -} - -function _getSite(bidRequest, topLocation) { - return { - page: topLocation.href, - domain: topLocation.hostname, - publisher: { - id: utils.getBidIdParameter('publisherId', bidRequest.params) - } - }; -} - -function _getSeller(bidRequest) { - return { - id: utils.getBidIdParameter('sellerId', bidRequest.params) - }; -} - -function _getSizes(bidRequest) { - if (!utils.isArray(bidRequest.sizes)) { - return undefined; - } - return bidRequest.sizes.filter(_isValidSize).map(size => { - return { - w: size[0], - h: size[1] - } - }); -} - -function _isValidSize(size) { - const isNumber = x => typeof x === 'number'; - return (size.length === 2 && isNumber(size[0]) && isNumber(size[1])); -} - -function _getDevice(_bidRequest) { - return { - ua: navigator.userAgent, - language: navigator['language'], - os: _getOs(navigator.userAgent.toLowerCase()), - osv: _getOsVersion(navigator.userAgent) - }; -} - -function _getRegs(bidderRequest) { - if (!bidderRequest || !bidderRequest.uspConsent) { - return undefined; - } - return { - ext: { - us_privacy: bidderRequest.uspConsent - } - }; -} - -function _getOs(userAgent) { - const osTable = { - 'android': /android/i, - 'ios': /iphone|ipad/i, - 'mac': /mac/i, - 'linux': /linux/i, - 'windows': /windows/i - }; - - return find(Object.keys(osTable), os => { - if (userAgent.match(osTable[os])) { - return os; - } - }) || 'etc'; -} - -function _getOsVersion(userAgent) { - const 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(userAgent)); - return cs ? cs.s : 'unknown'; -} - -registerBidder(spec); diff --git a/modules/nextrollIdSystem.js b/modules/nextrollIdSystem.js new file mode 100644 index 00000000000..5a59e216394 --- /dev/null +++ b/modules/nextrollIdSystem.js @@ -0,0 +1,50 @@ +/** + * This module adds Nextroll ID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/nextrollIdSystem + * @requires module:modules/userId + */ + +import { deepAccess } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const NEXTROLL_ID_LS_KEY = 'dca0.com'; +const KEY_PREFIX = 'AdID:' + +export const storage = getStorageManager(); + +/** @type {Submodule} */ +export const nextrollIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'nextrollId', + + /** + * decode the stored id value for passing to bid requests + * @function + * @return {{nextrollId: string} | undefined} + */ + decode(value) { + return value; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @returns {{id: {nextrollId: string} | undefined}} + */ + getId(config) { + const key = KEY_PREFIX + deepAccess(config, 'params.partnerId', 'undefined'); + const dataString = storage.getDataFromLocalStorage(NEXTROLL_ID_LS_KEY) || '{}'; + const data = JSON.parse(dataString); + const idValue = deepAccess(data, `${key}.value`); + + return { id: idValue ? {nextrollId: idValue} : undefined }; + } +}; + +submodule('userId', nextrollIdSubmodule); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index f1a9092e31d..619b46ecd1f 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -4,9 +4,10 @@ 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.6'; +const storage = getStorageManager(GVLID, BIDDER_CODE); +window.nobidVersion = '1.3.1'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -24,6 +25,15 @@ function nobidSetCookie(cname, cvalue, hours) { function nobidGetCookie(cname) { return storage.getCookie(cname); } +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); + } + } + return result; +} function nobidBuildRequests(bids, bidderRequest) { var serializeState = function(divIds, siteId, adunits) { var filterAdUnitsByIds = function(divIds, adUnits) { @@ -105,6 +115,23 @@ function nobidBuildRequests(bids, bidderRequest) { utils.logWarn('Could not parse screen dimensions, error details:', e); } } + var getEIDs = function(eids) { + if (utils.isArray(eids) && eids.length > 0) { + let src = []; + eids.forEach((eid) => { + let ids = []; + if (eid.uids) { + eid.uids.forEach(value => { + ids.push({'id': value.id + ''}); + }); + } + if (eid.source && ids.length > 0) { + src.push({source: eid.source, uids: ids}); + } + }); + return src; + } + } var state = {}; state['sid'] = siteId; state['l'] = topLocation(bidderRequest); @@ -118,10 +145,13 @@ 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')); + if (eids && eids.length > 0) state['eids'] = eids; return state; } function newAdunit(adunitObject, adunits) { @@ -270,6 +300,9 @@ function nobidInterpretResponse(response, bidRequest) { if (bid.videoCacheKey) { bidResponse.videoCacheKey = bid.videoCacheKey; } + if (bid.meta) { + bidResponse.meta = bid.meta; + } bidResponses.push(bidResponse); } @@ -296,19 +329,7 @@ window.addEventListener('message', function (event) { if (window.nobid && window.nobid.bidResponses) { var bid = window.nobid.bidResponses['' + adId]; if (bid && bid.adm2) { - var markup = null; - if (bid.is_combo && bid.adm_combo) { - for (var i in bid.adm_combo) { - var combo = bid.adm_combo[i]; - if (!combo.done) { - markup = combo.adm; - combo.done = true; - break; - } - } - } else { - markup = bid.adm2; - } + var markup = bid.adm2; if (markup) { event.source.postMessage('nbTagRenderer.renderAdInSafeFrame|' + markup, '*'); } @@ -318,6 +339,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. @@ -358,11 +383,18 @@ export const spec = { window.nobid.refreshCount++; const payloadString = JSON.stringify(payload).replace(/'|&|#/g, '') const endpoint = buildEndpoint(); + + let options = {}; + if (!nobidHasPurpose1Consent(bidderRequest)) { + options = { withCredentials: false }; + } + return { method: 'POST', url: endpoint, data: payloadString, - bidderRequest + bidderRequest, + options }; }, /** diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js new file mode 100644 index 00000000000..fbfa6ca8abc --- /dev/null +++ b/modules/novatiqIdSystem.js @@ -0,0 +1,88 @@ +/** + * This module adds novatiqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/novatiqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +/** @type {Submodule} */ +export const novatiqIdSubmodule = { + +/** +* 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(novatiqId, config) { + let responseObj = { + novatiq: { + snowflake: novatiqId + } + }; + return responseObj; + }, + + /** +* 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 + ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11 + 1e3).replace(/[018]/g, snowflakeId); + } + + const configParams = config.params || {}; + const srcId = this.getSrcId(configParams); + utils.logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + + let partnerhost; + partnerhost = window.location.hostname; + utils.logInfo('NOVATIQ partner hostname: ' + partnerhost); + + const novatiqId = snowflakeId(); + const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); + + utils.logInfo('NOVATIQ snowflake: ' + novatiqId); + return { 'id': novatiqId } + }, + + getSrcId(configParams) { + utils.logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + + function isHex(str) { + var a = parseInt(str, 16); + return (a.toString(16) === str) + } + + 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'); + } 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'); + } 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'); + } else { + srcId = configParams.sourceid; + } + return srcId + } +}; +submodule('userId', novatiqIdSubmodule); diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md new file mode 100644 index 00000000000..ce561a696e3 --- /dev/null +++ b/modules/novatiqIdSystem.md @@ -0,0 +1,36 @@ +# Novatiq Snowflake 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. + +## Novatiq Snowflake ID Configuration + +Enable by adding the Novatiq submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'novatiq', + params: { + sourceid '1a3', // change to the Partner Number you received from Novatiq + } + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | 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` | + +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..2e5342e6f9e --- /dev/null +++ b/modules/oguryBidAdapter.js @@ -0,0 +1,155 @@ +'use strict'; + +import { BANNER } from '../src/mediaTypes.js'; +import { getAdUnitSizes, logWarn, isFn } 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 MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; + +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.consentString}&source=prebid` + }] +} + +function buildRequests(validBidRequests, bidderRequest) { + const openRtbBidRequestBanner = { + id: bidderRequest.auctionId, + tmax: DEFAULT_TIMEOUT, + at: 2, + regs: { + ext: { + gdpr: 1 + }, + }, + site: { + domain: location.hostname + }, + user: { + ext: { + consent: '' + } + }, + imp: [] + }; + + if (bidderRequest.hasOwnProperty('gdprConsent') && + bidderRequest.gdprConsent.hasOwnProperty('gdprApplies')) { + openRtbBidRequestBanner.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } + + if (bidderRequest.hasOwnProperty('gdprConsent') && + bidderRequest.gdprConsent.hasOwnProperty('consentString') && + bidderRequest.gdprConsent.consentString.length > 0) { + openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString + } + + validBidRequests.forEach((bidRequest) => { + const sizes = getAdUnitSizes(bidRequest) + .map(size => ({ w: size[0], h: size[1] })); + + if (bidRequest.hasOwnProperty('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 + } + }); + } + }); + + 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 + }; + + 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 onBidWon(bid) { + if (bid && bid.hasOwnProperty('nurl') && bid.nurl.length > 0) ajax(bid['nurl'], null); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + getUserSyncs, + buildRequests, + interpretResponse, + getFloor, + onBidWon +} + +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 26b0a7dccc2..880f4992614 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -4,12 +4,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'oneVideo'; export const spec = { code: 'oneVideo', - VERSION: '3.0.3', + 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://cm.g.doubleclick.net/pixel?google_nid=adaptv_dbm&google_cm&google_sc', - SYNC_ENDPOINT2: 'https://pr-bh.ybp.yahoo.com/sync/adaptv_ortb/{combo_uid}', - SYNC_ENDPOINT3: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', + SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true', + SYNC_ENDPOINT2: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. @@ -18,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') { + utils.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')) { + utils.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') { + utils.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) { + utils.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') { + utils.logError('Failed validation: Adapter cannot send requests without bid.params.pubId'); return false; } @@ -50,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 => { @@ -76,7 +95,7 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(response, { bidRequest }) { + interpretResponse: function(response, {bidRequest}) { let bid; let size; let bidResponse; @@ -95,14 +114,16 @@ export const spec = { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: bid.price, - adId: bid.adid, creativeId: bid.crid, width: size.width, height: size.height, currency: response.cur, - ttl: 100, + 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' @@ -114,7 +135,6 @@ export const spec = { } else if (bid.adm) { bidResponse.vastXml = bid.adm; } - if (bidRequest.mediaTypes.video) { bidResponse.renderer = (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined; } @@ -129,24 +149,23 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, responses, consentData = {}) { - let { gdprApplies, consentString = '' } = consentData; + let { + gdprApplies, + consentString = '' + } = consentData; if (syncOptions.pixelEnabled) { return [{ type: 'image', url: spec.SYNC_ENDPOINT1 }, - { - type: 'image', - url: spec.SYNC_ENDPOINT2 - }, { type: 'image', url: `https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0` + encodeURI(`&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`) }, { type: 'image', - url: spec.SYNC_ENDPOINT3 + url: spec.SYNC_ENDPOINT2 }]; } } @@ -169,14 +188,17 @@ function getRequestData(bid, consentData, bidRequest) { let loc = bidRequest.refererInfo.referer; let page = (bid.params.site && bid.params.site.page) ? (bid.params.site.page) : (loc.href); let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : bidRequest.refererInfo.referer; + let getFloorRequestObject = { + currency: bid.params.cur || 'USD', + mediaType: 'video', + size: '*' + }; let bidData = { id: utils.generateUUID(), at: 2, - cur: bid.cur || 'USD', imp: [{ id: '1', secure: isSecure(), - bidfloor: bid.params.bidfloor, ext: { hb: 1, prebidver: '$prebid.version$', @@ -195,41 +217,56 @@ 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 = { mimes: bid.params.video.mimes, w: bid.params.video.playerWidth, @@ -248,6 +285,15 @@ function getRequestData(bid, consentData, bidRequest) { bidData.imp[0].banner.ext.minduration = bid.params.video.minduration } } + + if (utils.isFn(bid.getFloor)) { + let floorData = bid.getFloor(getFloorRequestObject); + bidData.imp[0].bidfloor = floorData.floor; + bidData.cur = floorData.currency; + } else { + bidData.imp[0].bidfloor = bid.params.bidfloor; + }; + if (bid.params.video.inventoryid) { bidData.imp[0].ext.inventoryid = bid.params.video.inventoryid } @@ -298,6 +344,7 @@ function getRequestData(bid, consentData, bidRequest) { } } if (bid.params.video.e2etest) { + utils.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; @@ -307,6 +354,33 @@ 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)) { + 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])) { + bidData.imp[0].ext.custom[key] = bid.params.video.custom[key]; + } + } + } + if (bid.params.video.content && utils.isPlainObject(bid.params.video.content)) { + bidData.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + 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)))) { + 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'); + } + } + } return bidData; } @@ -322,7 +396,7 @@ function newRenderer(bidRequest, bid) { bidRequest.renderer = {}; bidRequest.renderer.url = 'https://cdn.vidible.tv/prod/hb-outstream-renderer/renderer.js'; bidRequest.renderer.render = function(bid) { - setTimeout(function () { + setTimeout(function() { // eslint-disable-next-line no-undef o2PlayerRender(bid); }, 700) diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index 72f251aac04..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,22 +42,17 @@ 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", + key2: 123345 + } }, + bidfloor: 0.5, site: { id: 1, page: 'https://verizonmedia.com', @@ -53,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: [ @@ -70,22 +82,77 @@ 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, site: { id: 1, page: 'https://verizonmedia.com', @@ -108,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: [ @@ -116,12 +184,9 @@ 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: { id: 1, page: 'https://verizonmedia.com', @@ -155,6 +220,7 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to mimes: ['video/mp4', 'application/javascript'], display: 1 }, + bidfloor: 0.5, site: { id: 1, page: 'https://verizonmedia.com', @@ -180,6 +246,7 @@ var adUnits = [ video: { context: "instream", playerSize: [480, 640] + mimes: ['video/mp4', 'application/javascript'], } }, bids: [ @@ -187,12 +254,8 @@ var adUnits = [ bidder: 'oneVideo', params: { video: { - playerWidth: 300, - playerHeight: 250, - mimes: ['video/mp4', 'application/javascript'], e2etest: true } - pubId: 'YOUR_PUBLISHER_ID' } } ] @@ -216,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: [ @@ -224,13 +290,9 @@ 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: { id: 1, page: 'https://verizonmedia.com', @@ -268,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: [ @@ -276,17 +341,14 @@ 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: { id: 1, page: 'https://verizonmedia.com', referrer: 'https://verizonmedia.com' - }, + }, pubId: 'HBExchange' } } @@ -294,3 +356,87 @@ var adUnits = [ } ] ``` +# Content Object Support +The oneVideoBidAdapter supports passing of OpenRTB V2.5 Content Object. + +``` +const adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [1, 2], + } + }, + bids: [{ + bidder: 'oneVideo', + params: { + video: { + ttl: 300, + content: { + id: "1234", + title: "Title", + series: "Series", + season: "Season", + episode: 1 + cat: [ + "IAB1", + "IAB1-1", + "IAB1-2", + "IAB2", + "IAB2-1" + ], + genre: "Genre", + contentrating: "C-Rating", + language: "EN", + prodq: 1, + context: 1, + livestream: 0, + len: 360, + ext: { + network: "ext-network", + channel: "ext-channel" + } + } + }, + bidfloor: 0.5, + pubId: 'HBExchange' + } + } + }] + }] +``` + + +# TTL Support +The oneVideoBidAdapter 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 +You can enter any number between 1 - 3600 (seconds) +``` +const adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [1, 2], + } + }, + bids: [{ + bidder: 'oneVideo', + params: { + video: { + ttl: 300 + }, + bidfloor: 0.5, + pubId: 'HBExchange' + } + }] + }] +``` + diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 6280dd12268..7a0b78ceeff 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -4,11 +4,17 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import find from 'core-js-pure/features/array/find.js'; -const { registerBidder } = require('../src/adapters/bidderFactory.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/'; const BIDDER_CODE = 'onetag'; +const GVLID = 241; + +const storage = getStorageManager(GVLID); /** * Determines whether or not the given bid request is valid. @@ -59,17 +65,18 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } - if (bidderRequest && bidderRequest.userId) { - payload.userId = bidderRequest.userId; - } - if (window.localStorage) { - payload.onetagSid = window.localStorage.getItem('onetag_sid'); + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userId) { + payload.userId = createEidsArray(validBidRequests[0].userId); } - const payloadString = JSON.stringify(payload); + try { + if (storage.hasLocalStorage()) { + payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); + } + } catch (e) {} return { method: 'POST', url: ENDPOINT, - data: payloadString + data: JSON.stringify(payload) } } @@ -83,47 +90,38 @@ function interpretResponse(serverResponse, bidderRequest) { if (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0) { return bids; } - body.bids.forEach(({ - requestId, - cpm, - width, - height, - creativeId, - dealId, - currency, - mediaType, - ttl, - rendererUrl, - ad, - vastUrl, - videoCacheKey - }) => { + body.bids.forEach(bid => { const responseBid = { - requestId, - cpm, - width, - height, - creativeId, - dealId: dealId == null ? dealId : '', - currency, - netRevenue: false, + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + dealId: bid.dealId == null ? bid.dealId : '', + currency: bid.currency, + netRevenue: bid.netRevenue || false, + mediaType: bid.mediaType, meta: { - mediaType + mediaType: bid.mediaType, + advertiserDomains: bid.adomain }, - ttl: ttl || 300 + ttl: bid.ttl || 300 }; - if (mediaType === BANNER) { - responseBid.ad = ad; - } else if (mediaType === VIDEO) { - const {context, adUnitCode} = find(requestData.bids, (item) => item.bidId === requestId); + if (bid.mediaType === BANNER) { + responseBid.ad = bid.ad; + } else if (bid.mediaType === VIDEO) { + const {context, adUnitCode} = find(requestData.bids, (item) => + item.bidId === bid.requestId && + item.type === VIDEO + ); if (context === INSTREAM) { - responseBid.vastUrl = vastUrl; - responseBid.videoCacheKey = videoCacheKey; + responseBid.vastUrl = bid.vastUrl; + responseBid.videoCacheKey = bid.videoCacheKey; } else if (context === OUTSTREAM) { - responseBid.vastXml = ad; - responseBid.vastUrl = vastUrl; - if (rendererUrl) { - responseBid.renderer = createRenderer({requestId, rendererUrl, adUnitCode}); + responseBid.vastXml = bid.ad; + responseBid.vastUrl = bid.vastUrl; + if (bid.rendererUrl) { + responseBid.renderer = createRenderer({ ...bid, adUnitCode }); } } } @@ -141,37 +139,41 @@ function createRenderer(bid, rendererOptions = {}) { loaded: false }); try { - renderer.setRender(onetagRenderer); + renderer.setRender(({renderer, width, height, vastXml, adUnitCode}) => { + renderer.push(() => { + window.onetag.Player.init({ + ...bid, + width, + height, + vastXml, + nodeId: adUnitCode, + config: renderer.getConfig() + }); + }); + }); } catch (e) { } return renderer; } -function onetagRenderer({renderer, width, height, vastXml, adUnitCode}) { - renderer.push(() => { - window.onetag.Player.init({ - width, - height, - vastXml, - nodeId: adUnitCode, - config: renderer.getConfig() - }); - }); -} - function getFrameNesting() { - let frame = window; + let topmostFrame = window; + let parent = window.parent; + let currentFrameNesting = 0; try { - while (frame !== frame.top) { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; // eslint-disable-next-line no-unused-expressions - frame.location.href; - frame = frame.parent; + parent.location.href; + topmostFrame = topmostFrame.parent; } - } catch (e) {} + } catch (e) { + currentFrameNesting = parent === topmostFrame.top ? 1 : 2; + } return { - topmostFrame: frame, - currentFrameNesting: frame.top === frame ? 1 : 2 + topmostFrame, + currentFrameNesting } } @@ -198,8 +200,15 @@ function getDocumentVisibility(window) { function getPageInfo() { const { topmostFrame, currentFrameNesting } = getFrameNesting(); return { - location: encodeURIComponent(topmostFrame.location.href), - referrer: encodeURIComponent(topmostFrame.document.referrer) || '0', + location: topmostFrame.location.href, + referrer: + topmostFrame.document.referrer !== '' + ? topmostFrame.document.referrer + : null, + ancestorOrigin: + window.location.ancestorOrigins && window.location.ancestorOrigins[0] != null + ? window.location.ancestorOrigins[0] + : null, masked: currentFrameNesting, wWidth: topmostFrame.innerWidth, wHeight: topmostFrame.innerHeight, @@ -216,7 +225,11 @@ function getPageInfo() { docHidden: getDocumentVisibility(topmostFrame), docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, hLength: history.length, - timing: getTiming() + timing: getTiming(), + version: { + prebid: '$prebid.version$', + adapter: '1.1.0' + } }; } @@ -227,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; }); @@ -243,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); @@ -256,6 +266,7 @@ function setGeneralInfo(bidRequest) { this['auctionId'] = bidRequest.auctionId; this['transactionId'] = bidRequest.transactionId; this['pubId'] = params.pubId; + this['ext'] = params.ext; if (params.pubClick) { this['click'] = params.pubClick; } @@ -326,7 +337,7 @@ function parseSizes(bid) { function getSizes(sizes) { const ret = []; - for (let i = 0, lenght = sizes.length; i < lenght; i++) { + for (let i = 0; i < sizes.length; i++) { const size = sizes[i]; ret.push({width: size[0], height: size[1]}) } @@ -362,6 +373,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index 55fca29fbf3..548c0170c05 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -46,7 +46,7 @@ function buildRequests(bidReqs, bidderRequest) { }, tagid: String(bid.adUnitCode) }; - const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + const bidFloor = _getBidFloor(bid); if (bidFloor) { imp.bidfloor = bidFloor; } @@ -117,7 +117,10 @@ function interpretResponse(serverResponse) { netRevenue: true, mediaType: BANNER, ad: _getAdMarkup(onomagicBid), - ttl: 60 + ttl: 60, + meta: { + advertiserDomains: onomagicBid && onomagicBid.adomain ? onomagicBid.adomain : [] + } }); }); } @@ -243,4 +246,20 @@ function _getPercentInView(element, topWin, { w, h } = {}) { return 0; } +function _getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.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 new file mode 100644 index 00000000000..7195d6ac177 --- /dev/null +++ b/modules/ooloAnalyticsAdapter.js @@ -0,0 +1,547 @@ +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' + +const baseUrl = 'https://pbdata.oolo.io/' +const ENDPOINTS = { + CONFIG: 'https://config-pbdata.oolo.io', + PAGE_DATA: baseUrl + 'page', + AUCTION: baseUrl + 'auctionData', + BID_WON: baseUrl + 'bidWonData', + AD_RENDER_FAILED: baseUrl + 'adRenderFailedData', + RAW: baseUrl + 'raw', + HBCONFIG: baseUrl + 'hbconfig', +} + +const pbModuleVersion = '1.0.0' +const prebidVersion = '$prebid.version$' +const analyticsType = 'endpoint' +const ADAPTER_CODE = 'oolo' +const AUCTION_END_SEND_TIMEOUT = 1500 +export const PAGEVIEW_ID = +generatePageViewId() + +const { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + NO_BID, + BID_WON, + BID_TIMEOUT, + AD_RENDER_FAILED +} = CONSTANTS.EVENTS + +const SERVER_EVENTS = { + AUCTION: 'auction', + WON: 'bidWon', + AD_RENDER_FAILED: 'adRenderFailed' +} + +const SERVER_BID_STATUS = { + NO_BID: 'noBid', + BID_REQUESTED: 'bidRequested', + BID_RECEIVED: 'bidReceived', + BID_TIMEDOUT: 'bidTimedOut', + BID_WON: 'bidWon' +} + +let auctions = {} +let initOptions = {} +let eventsQueue = [] + +const onAuctionInit = (args) => { + const { auctionId, adUnits, timestamp } = args + + let auction = auctions[auctionId] = { + ...args, + adUnits: {}, + auctionStart: timestamp, + _sentToServer: false + } + + handleCustomFields(auction, AUCTION_INIT, args) + + utils._each(adUnits, adUnit => { + auction.adUnits[adUnit.code] = { + ...adUnit, + auctionId, + adunid: adUnit.code, + bids: {}, + } + }) +} + +const onBidRequested = (args) => { + const { auctionId, bids, start, timeout } = args + const _start = start || Date.now() + const auction = auctions[auctionId] + const auctionAdUnits = auction.adUnits + + bids.forEach(bid => { + const { adUnitCode } = bid + const bidId = parseBidId(bid) + + auctionAdUnits[adUnitCode].bids[bidId] = { + ...bid, + timeout, + start: _start, + rs: _start - auction.auctionStart, + bidStatus: SERVER_BID_STATUS.BID_REQUESTED, + } + }) +} + +const onBidResponse = (args) => { + const { auctionId, adUnitCode } = args + const auction = auctions[auctionId] + const bidId = parseBidId(args) + let bid = auction.adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_RECEIVED, + end: args.responseTimestamp, + re: args.responseTimestamp - auction.auctionStart + }) +} + +const onNoBid = (args) => { + const { auctionId, adUnitCode } = args + const bidId = parseBidId(args) + const end = Date.now() + const auction = auctions[auctionId] + let bid = auction.adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.NO_BID, + end, + re: end - auction.auctionStart + }) +} + +const onBidWon = (args) => { + const { auctionId, adUnitCode } = args + const bidId = parseBidId(args) + const bid = auctions[auctionId].adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_WON, + isW: true, + isH: true + }) + + if (auctions[auctionId]._sentToServer) { + const payload = { + auctionId, + adunid: adUnitCode, + bid: mapBid(bid, BID_WON) + } + + sendEvent(SERVER_EVENTS.WON, payload) + } +} + +const onBidTimeout = (args) => { + utils._each(args, bid => { + const { auctionId, adUnitCode } = bid + const bidId = parseBidId(bid) + let bidCache = auctions[auctionId].adUnits[adUnitCode].bids[bidId] + + Object.assign(bidCache, bid, { + bidStatus: SERVER_BID_STATUS.BID_TIMEDOUT, + }) + }) +} + +const onAuctionEnd = (args) => { + const { auctionId, adUnits, ...restAuctionEnd } = args + + Object.assign(auctions[auctionId], restAuctionEnd, { + auctionEnd: args.auctionEnd || Date.now() + }) + + // wait for bidWon before sending to server + setTimeout(() => { + auctions[auctionId]._sentToServer = true + const finalAuctionData = buildAuctionData(auctions[auctionId]) + + sendEvent(SERVER_EVENTS.AUCTION, finalAuctionData) + }, initOptions.serverConfig.BID_WON_TIMEOUT || AUCTION_END_SEND_TIMEOUT) +} + +const onAdRenderFailed = (args) => { + const data = utils.deepClone(args) + data.timestamp = Date.now() + + if (data.bid) { + data.bid = mapBid(data.bid, AD_RENDER_FAILED) + } + + sendEvent(SERVER_EVENTS.AD_RENDER_FAILED, data) +} + +var ooloAdapter = Object.assign( + adapter({ analyticsType }), { + track({ eventType, args }) { + // wait for server configuration before processing the events + if (typeof initOptions.serverConfig !== 'undefined' && eventsQueue.length === 0) { + handleEvent(eventType, args) + } else { + eventsQueue.push({ eventType, args }) + } + } + } +) + +function handleEvent(eventType, args) { + try { + const { sendRaw } = initOptions.serverConfig.events[eventType] + if (sendRaw) { + sendEvent(eventType, args, true) + } + } catch (e) { } + + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args) + break + case BID_REQUESTED: + onBidRequested(args) + break + case NO_BID: + onNoBid(args) + break + case BID_RESPONSE: + onBidResponse(args) + break + case BID_WON: + onBidWon(args) + break + case BID_TIMEOUT: + onBidTimeout(args) + break + case AUCTION_END: + onAuctionEnd(args) + break + case AD_RENDER_FAILED: + onAdRenderFailed(args) + break + } +} + +function sendEvent(eventType, args, isRaw) { + let data = utils.deepClone(args) + + Object.assign(data, buildCommonDataProperties(), { + eventType + }) + + if (isRaw) { + let rawEndpoint + try { + const { endpoint, omitRawFields } = initOptions.serverConfig.events[eventType] + rawEndpoint = endpoint + handleCustomRawFields(data, omitRawFields) + } catch (e) { } + ajaxCall(rawEndpoint || ENDPOINTS.RAW, () => { }, JSON.stringify(data)) + } else { + let endpoint + if (eventType === SERVER_EVENTS.AD_RENDER_FAILED) { + endpoint = ENDPOINTS.AD_RENDER_FAILED + } else if (eventType === SERVER_EVENTS.WON) { + endpoint = ENDPOINTS.BID_WON + } else { + endpoint = ENDPOINTS.AUCTION + } + + ajaxCall(endpoint, () => { }, JSON.stringify(data)) + } +} + +function checkEventsQueue() { + while (eventsQueue.length) { + const event = eventsQueue.shift() + handleEvent(event.eventType, event.args) + } +} + +function buildAuctionData(auction) { + const auctionData = utils.deepClone(auction) + const keysToRemove = ['adUnitCodes', 'auctionStatus', 'bidderRequests', 'bidsReceived', 'noBids', 'winningBids', 'timestamp', 'config'] + + keysToRemove.forEach(key => { + delete auctionData[key] + }) + + handleCustomFields(auctionData, AUCTION_END, auction) + + // turn bids object into array of objects + Object.keys(auctionData.adUnits).forEach(adUnit => { + const adUnitObj = auctionData.adUnits[adUnit] + adUnitObj.bids = Object.keys(adUnitObj.bids).map(key => mapBid(adUnitObj.bids[key])) + delete adUnitObj['adUnitCode'] + delete adUnitObj['code'] + delete adUnitObj['transactionId'] + }) + + // turn adUnits objects into array of adUnits + auctionData.adUnits = Object.keys(auctionData.adUnits).map(key => auctionData.adUnits[key]) + + return auctionData +} + +function buildCommonDataProperties() { + return { + pvid: PAGEVIEW_ID, + pid: initOptions.pid, + pbModuleVersion + } +} + +function buildLogMessage(message) { + return `oolo: ${message}` +} + +function parseBidId(bid) { + return bid.bidId || bid.requestId +} + +function mapBid({ + bidStatus, + start, + end, + mediaType, + creativeId, + originalCpm, + originalCurrency, + source, + netRevenue, + currency, + width, + height, + timeToRespond, + responseTimestamp, + ...rest +}, eventType) { + const bidObj = { + bst: bidStatus, + s: start, + e: responseTimestamp || end, + mt: mediaType, + crId: creativeId, + oCpm: originalCpm, + oCur: originalCurrency, + src: source, + nrv: netRevenue, + cur: currency, + w: width, + h: height, + ttr: timeToRespond, + ...rest, + } + + delete bidObj['bidRequestsCount'] + delete bidObj['bidderRequestId'] + delete bidObj['bidderRequestsCount'] + delete bidObj['bidderWinsCount'] + delete bidObj['schain'] + delete bidObj['refererInfo'] + delete bidObj['statusMessage'] + delete bidObj['status'] + delete bidObj['adUrl'] + delete bidObj['ad'] + delete bidObj['usesGenericKeys'] + delete bidObj['requestTimestamp'] + + try { + handleCustomFields(bidObj, eventType || BID_RESPONSE, rest) + } catch (e) { } + + return bidObj +} + +function handleCustomFields(obj, eventType, args) { + try { + const { pickFields, omitFields } = initOptions.serverConfig.events[eventType] + + if (pickFields && obj && args) { + Object.assign(obj, utils.pick(args, pickFields)) + } + + if (omitFields && obj && args) { + omitFields.forEach(field => { + utils.deepSetValue(obj, field, undefined) + }) + } + } catch (e) { } +} + +function handleCustomRawFields(obj, omitRawFields) { + try { + if (omitRawFields && obj) { + omitRawFields.forEach(field => { + utils.deepSetValue(obj, field, undefined) + }) + } + } catch (e) { } +} + +function getServerConfig() { + const defaultConfig = { events: {} } + + ajaxCall( + ENDPOINTS.CONFIG + '?pid=' + initOptions.pid, + { + success: function (data) { + try { + initOptions.serverConfig = JSON.parse(data) || defaultConfig + } catch (e) { + initOptions.serverConfig = defaultConfig + } + checkEventsQueue() + }, + error: function () { + initOptions.serverConfig = defaultConfig + checkEventsQueue() + } + }, + null + ) +} + +function sendPage() { + setTimeout(() => { + const payload = { + timestamp: Date.now(), + screenWidth: window.screen.width, + screenHeight: window.screen.height, + url: window.location.href, + protocol: window.location.protocol, + origin: utils.getOrigin(), + referrer: getTopWindowReferrer(), + pbVersion: prebidVersion, + } + + Object.assign(payload, buildCommonDataProperties(), getPagePerformance()) + ajaxCall(ENDPOINTS.PAGE_DATA, () => { }, JSON.stringify(payload)) + }, 0) +} + +function sendHbConfigData() { + const conf = {} + const pbjsConfig = config.getConfig() + + Object.keys(pbjsConfig).forEach(key => { + if (key[0] !== '_') { + conf[key] = pbjsConfig[key] + } + }) + + ajaxCall(ENDPOINTS.HBCONFIG, () => { }, JSON.stringify(conf)) +} + +function getPagePerformance() { + let timing + + try { + timing = window.top.performance.timing + } catch (e) { } + + if (!timing) { + return + } + + const { navigationStart, domContentLoadedEventEnd, loadEventEnd } = timing + const domContentLoadTime = domContentLoadedEventEnd - navigationStart + const pageLoadTime = loadEventEnd - navigationStart + + return { + domContentLoadTime, + pageLoadTime, + } +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer + } catch (e) { + return '' + } +} + +function generatePageViewId(min = 10000, max = 90000) { + var randomNumber = Math.floor((Math.random() * max) + min) + var currentdate = new Date() + var currentTime = { + getDate: currentdate.getDate(), + getMonth: currentdate.getMonth(), + getFullYear: currentdate.getFullYear(), + getHours: currentdate.getHours(), + getMinutes: currentdate.getMinutes(), + getSeconds: currentdate.getSeconds(), + getMilliseconds: currentdate.getMilliseconds() + } + return ((currentTime.getDate <= 9) ? '0' + (currentTime.getDate) : (currentTime.getDate)) + '' + + (currentTime.getMonth + 1 <= 9 ? '0' + (currentTime.getMonth + 1) : (currentTime.getMonth + 1)) + '' + + currentTime.getFullYear % 100 + '' + + (currentTime.getHours <= 9 ? '0' + currentTime.getHours : currentTime.getHours) + '' + + (currentTime.getMinutes <= 9 ? '0' + currentTime.getMinutes : currentTime.getMinutes) + '' + + (currentTime.getSeconds <= 9 ? '0' + currentTime.getSeconds : currentTime.getSeconds) + '' + + (currentTime.getMilliseconds % 100 <= 9 ? '0' + (currentTime.getMilliseconds % 100) : (currentTime.getMilliseconds % 100)) + '' + + (randomNumber) +} + +function ajaxCall(endpoint, callback, data, options = {}) { + if (data) { + options.contentType = 'application/json' + } + + return ajax(endpoint, callback, data, options) +} + +ooloAdapter.originEnableAnalytics = ooloAdapter.enableAnalytics +ooloAdapter.enableAnalytics = function (config) { + ooloAdapter.originEnableAnalytics(config) + initOptions = config ? config.options : {} + + if (!initOptions.pid) { + utils.logError(buildLogMessage('enableAnalytics missing config object with "pid"')) + return + } + + getServerConfig() + sendHbConfigData() + + if (document.readyState === 'complete') { + sendPage() + } else { + window.addEventListener('load', sendPage) + } + + utils.logInfo(buildLogMessage('enabled analytics adapter'), config) + ooloAdapter.enableAnalytics = function () { + utils.logInfo(buildLogMessage('Analytics adapter already enabled..')) + } +} + +ooloAdapter.originDisableAnalytics = ooloAdapter.disableAnalytics +ooloAdapter.disableAnalytics = function () { + auctions = {} + initOptions = {} + ooloAdapter.originDisableAnalytics() +} + +adapterManager.registerAnalyticsAdapter({ + adapter: ooloAdapter, + code: ADAPTER_CODE +}) + +// export for testing +export { + buildAuctionData, + generatePageViewId +} + +export default ooloAdapter diff --git a/modules/ooloAnalyticsAdapter.md b/modules/ooloAnalyticsAdapter.md new file mode 100644 index 00000000000..1ffb9cbe050 --- /dev/null +++ b/modules/ooloAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +Module Name: oolo Analytics Adapter +Module Type: Analytics Adapter +Maintainer: admin@oolo.io + +# Description + +Analytics adapter for oolo. + +oolo is an anomaly detection based monitoring solution for web publishers, built by adops for adops. Its purpose is to eliminate most of the daily manual work that teams are required to do, while increasing the overall performance, due to full & faster detection of problems and opportunities. + +Contact admin@oolo.io for information. + +# Usage + +```javascript +pbjs.enableAnalytics({ + provider: 'oolo', + options: { + pid: 12345 // id provided by oolo + } +}) +``` 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..ef776ea81f5 --- /dev/null +++ b/modules/openwebBidAdapter.js @@ -0,0 +1,247 @@ +import * as utils 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 utils.isNumber(utils.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) { + utils.isArray(serverResponses) && serverResponses.forEach((response) => { + if (response.body) { + if (utils.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 (!utils.isArray(serverResponse)) { + return parseRTBResponse(serverResponse, adapterRequest); + } + + serverResponse.forEach(serverBidResponse => { + bids = utils.flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); + }); + + return bids; + }, + + transformBidParams(params) { + return utils.convertTypes({ + 'aid': 'number', + }, params); + } +}; + +function parseRTBResponse(serverResponse, adapterRequest) { + const isEmptyResponse = !serverResponse || !utils.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: utils.deepAccess(adapterRequest, 'refererInfo.referer') + }; + if (config.getConfig('coppa') === true) { + tag.Coppa = 1; + } + if (utils.deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { + tag.GDPR = 1; + tag.GDPRConsent = utils.deepAccess(adapterRequest, 'gdprConsent.consentString'); + } + if (utils.deepAccess(adapterRequest, 'uspConsent')) { + tag.USP = utils.deepAccess(adapterRequest, 'uspConsent'); + } + if (utils.deepAccess(bidRequests[0], 'schain')) { + tag.Schain = utils.deepAccess(bidRequests[0], 'schain'); + } + if (utils.deepAccess(bidRequests[0], 'userId')) { + tag.UserIds = utils.deepAccess(bidRequests[0], 'userId'); + } + if (utils.deepAccess(bidRequests[0], 'userIdAsEids')) { + tag.UserEids = utils.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 = utils.deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; + const sizes = mediaType === VIDEO ? utils.deepAccess(bidReq, 'mediaTypes.video.playerSize') : utils.deepAccess(bidReq, 'mediaTypes.banner.sizes'); + const bidReqParams = { + 'CallbackId': bidReq.bidId, + 'Aid': bidReq.params.aid, + 'AdType': mediaType, + 'Sizes': utils.parseSizesInput(sizes).join(',') + }; + + bidReqParams.PlacementId = bidReq.adUnitCode; + if (bidReq.params.iframe) { + bidReqParams.AdmType = 'iframe'; + } + if (mediaType === VIDEO) { + const context = utils.deepAccess(bidReq, 'mediaTypes.video.context'); + if (context === ADPOD) { + bidReqParams.Adpod = utils.deepAccess(bidReq, 'mediaTypes.video'); + } + } + return bidReqParams; +} + +/** + * Prepare all parameters for request + * @param bidderRequest {object} + * @returns {object} + */ +function getMediaType(bidderRequest) { + return utils.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 = utils.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 7addfe68bc6..0c01d4b461e 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,260 +1,753 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; -import * as utils from '../src/utils.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 + ENDED: 'ended', // all auction requests have been accounted for + COMPLETED: 'completed' // all slots have rendered +}; + +const ADAPTER_VERSION = '0.1'; +const SCHEMA_VERSION = '0.1'; + +const AUCTION_END_WAIT_TIME = 1000; +const URL_PARAM = ''; +const ANALYTICS_TYPE = 'endpoint'; +const ENDPOINT = 'https://prebid.openx.net/ox/analytics/'; +// Event Types const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, AUCTION_END, BID_WON } } = CONSTANTS; - const SLOT_LOADED = 'slotOnload'; -const ENDPOINT = 'https://ads.openx.net/w/1.0/pban'; +const UTM_TAGS = [ + 'utm_campaign', + 'utm_source', + 'utm_medium', + 'utm_term', + 'utm_content' +]; +const UTM_TO_CAMPAIGN_PROPERTIES = { + 'utm_campaign': 'name', + 'utm_source': 'source', + 'utm_medium': 'medium', + 'utm_term': 'term', + 'utm_content': 'content' +}; -let initOptions; +/** + * @typedef {Object} OxAnalyticsConfig + * @property {string} orgId + * @property {string} publisherPlatformId + * @property {number} publisherAccountId + * @property {string} configId + * @property {string} optimizerConfig + * @property {number} sampling + * @property {Object} campaign + * @property {number} payloadWaitTime + * @property {number} payloadWaitTimePadding + * @property {Array} adUnits + */ + +/** + * @type {OxAnalyticsConfig} + */ +const DEFAULT_ANALYTICS_CONFIG = { + orgId: void (0), + publisherPlatformId: void (0), + publisherAccountId: void (0), + sampling: 0.05, // default sampling rate of 5% + testCode: 'default', + campaign: {}, + adUnits: [], + payloadWaitTime: AUCTION_END_WAIT_TIME, + payloadWaitTimePadding: 2000 +}; +// Initialization +/** + * @type {OxAnalyticsConfig} + */ +let analyticsConfig; let auctionMap = {}; +let auctionOrder = 1; // tracks the number of auctions ran on the page -function onAuctionInit({ auctionId }) { - auctionMap[auctionId] = { - adUnitMap: {} - }; +let googletag = window.googletag || {}; +googletag.cmd = googletag.cmd || []; + +let openxAdapter = Object.assign(adapter({ urlParam: URL_PARAM, analyticsType: ANALYTICS_TYPE })); + +openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; + +openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { + if (isValidConfig(adapterConfig)) { + analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...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); + + // override track method with v2 handlers + openxAdapter.track = prebidAnalyticsEventHandler; + + googletag.cmd.push(function () { + let pubads = googletag.pubads(); + + if (pubads.addEventListener) { + pubads.addEventListener(SLOT_LOADED, args => { + openxAdapter.track({eventType: SLOT_LOADED, args}); + utils.logInfo('OX: SlotOnLoad event triggered'); + }); + } + }); + + openxAdapter.originEnableAnalytics(adapterConfig); + } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: openxAdapter, + code: 'openx' +}); + +export default openxAdapter; + +/** + * Test Helper Functions + */ + +// reset the cache for unit tests +openxAdapter.reset = function() { + auctionMap = {}; + auctionOrder = 1; +}; + +/** + * Private Functions + */ + +function isValidConfig({options: analyticsOptions}) { + let hasOrgId = analyticsOptions && analyticsOptions.orgId !== void (0); + + const fieldValidations = [ + // tuple of property, type, required + ['orgId', 'string', hasOrgId], + ['publisherPlatformId', 'string', !hasOrgId], + ['publisherAccountId', 'number', !hasOrgId], + ['configId', 'string', false], + ['optimizerConfig', 'string', false], + ['sampling', 'number', false], + ['adIdKey', 'string', false], + ['payloadWaitTime', 'number', false], + ['payloadWaitTimePadding', 'number', false], + ]; + + let failedValidation = find(fieldValidations, ([property, type, required]) => { + // if required, the property has to exist + // if property exists, type check value + return (required && !analyticsOptions.hasOwnProperty(property)) || + /* eslint-disable valid-typeof */ + (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type); + }); + if (failedValidation) { + let [property, type, required] = failedValidation; + + if (required) { + utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); + } else { + utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); + } + } + + return !failedValidation; +} + +function buildCampaignFromUtmCodes() { + const location = utils.getWindowLocation(); + const queryParams = utils.parseQS(location && location.search); + let campaign = {}; + + UTM_TAGS.forEach(function(utmKey) { + let utmValue = queryParams[utmKey]; + if (utmValue) { + let key = UTM_TO_CAMPAIGN_PROPERTIES[utmKey]; + campaign[key] = decodeURIComponent(utmValue); + } + }); + return campaign; +} + +function detectMob() { + if ( + navigator.userAgent.match(/Android/i) || + navigator.userAgent.match(/webOS/i) || + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/iPod/i) || + navigator.userAgent.match(/BlackBerry/i) || + navigator.userAgent.match(/Windows Phone/i) + ) { + return true; + } else { + return false; + } } -function onBidRequested({ auctionId, auctionStart, bids, start }) { - const adUnitMap = auctionMap[auctionId]['adUnitMap']; +function detectOS() { + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; +} + +function detectBrowser() { + var isChrome = + /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); + var isCriOS = navigator.userAgent.match('CriOS'); + var isSafari = + /Safari/.test(navigator.userAgent) && + /Apple Computer/.test(navigator.vendor); + var isFirefox = /Firefox/.test(navigator.userAgent); + var isIE = + /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent); + var isEdge = /Edge/.test(navigator.userAgent); + if (isIE) return 'Internet Explorer'; + if (isEdge) return 'Microsoft Edge'; + if (isCriOS) return 'Chrome'; + if (isSafari) return 'Safari'; + if (isFirefox) return 'Firefox'; + if (isChrome) return 'Chrome'; + return 'Others'; +} - bids.forEach(bid => { - const { adUnitCode, bidId, bidder, params, transactionId } = bid; +function prebidAnalyticsEventHandler({eventType, args}) { + utils.logMessage(eventType, Object.assign({}, args)); + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args); + break; + case BID_REQUESTED: + onBidRequested(args); + break; + case BID_RESPONSE: + onBidResponse(args); + break; + case BID_TIMEOUT: + onBidTimeout(args); + break; + case AUCTION_END: + onAuctionEnd(args); + break; + case BID_WON: + onBidWon(args); + break; + case SLOT_LOADED: + onSlotLoadedV2(args); + break; + } +} - adUnitMap[adUnitCode] = adUnitMap[adUnitCode] || { - auctionId, - auctionStart, - transactionId, - bidMap: {} +/** + * @typedef {Object} PbAuction + * @property {string} auctionId - Auction ID of the request this bid responded to + * @property {number} timestamp //: 1586675964364 + * @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364 + * @property {string} auctionStatus //: "inProgress" + * @property {Array} adUnits //: [{…}] + * @property {string} adUnitCodes //: ["video1"] + * @property {string} labels //: undefined + * @property {Array} bidderRequests //: (2) [{…}, {…}] + * @property {Array} noBids //: [] + * @property {Array} bidsReceived //: [] + * @property {Array} winningBids //: [] + * @property {number} timeout //: 3000 + * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1}/* + */ + +function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { + auctionMap[auctionId] = { + id: auctionId, + startTime, + endTime: void (0), + timeout, + auctionOrder, + userIds: [], + adUnitCodesCount: adUnitCodes.length, + adunitCodesRenderedCount: 0, + state: AUCTION_STATES.INIT, + auctionSendDelayTimer: void (0), + }; + + // setup adunit properties in map + auctionMap[auctionId].adUnitCodeToAdUnitMap = adUnitCodes.reduce((obj, adunitCode) => { + obj[adunitCode] = { + code: adunitCode, + adPosition: void (0), + bidRequestsMap: {} }; + return obj; + }, {}); - adUnitMap[adUnitCode]['bidMap'][bidId] = { + auctionOrder++; +} + +/** + * @typedef {Object} PbBidRequest + * @property {string} auctionId - Auction ID of the request this bid responded to + * @property {number} auctionStart //: 1586675964364 + * @property {Object} refererInfo + * @property {PbBidderRequest} bids + * @property {number} start - Start timestamp of the bidder request + * + */ + +/** + * @typedef {Object} PbBidderRequest + * @property {string} adUnitCode - Name of div or google adunit path + * @property {string} bidder - Bame of bidder + * @property {string} bidId - Identifies the bid request + * @property {Object} mediaTypes + * @property {Object} params + * @property {string} src + * @property {Object} userId - Map of userId module to module object + */ + +/** + * Tracks the bid request + * @param {PbBidRequest} bidRequest + */ +function onBidRequested(bidRequest) { + const {auctionId, bids: bidderRequests, start, timeout} = bidRequest; + const auction = auctionMap[auctionId]; + const adUnitCodeToAdUnitMap = auction.adUnitCodeToAdUnitMap; + + bidderRequests.forEach(bidderRequest => { + const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src, userId } = bidderRequest; + + auction.userIds.push(userId); + adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId] = { bidder, params, - requestTimestamp: start + mediaTypes, + source: src, + startTime: start, + timedOut: false, + timeLimit: timeout, + bids: {} }; }); } -function onBidResponse({ - auctionId, - adUnitCode, - requestId: bidId, - cpm, - creativeId, - responseTimestamp, - ts, - adId -}) { - const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode]; - const bid = adUnit['bidMap'][bidId]; - bid.cpm = cpm; - bid.creativeId = creativeId; - bid.responseTimestamp = responseTimestamp; - bid.ts = ts; - bid.adId = adId; +/** + * + * @param {BidResponse} bidResponse + */ +function onBidResponse(bidResponse) { + let { + auctionId, + adUnitCode, + requestId, + cpm, + creativeId, + requestTimestamp, + responseTimestamp, + ts, + mediaType, + dealId, + ttl, + netRevenue, + currency, + originalCpm, + originalCurrency, + width, + height, + timeToRespond: latency, + adId, + meta + } = bidResponse; + + auctionMap[auctionId].adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId].bids[adId] = { + cpm, + creativeId, + requestTimestamp, + responseTimestamp, + ts, + adId, + meta, + mediaType, + dealId, + ttl, + netRevenue, + currency, + originalCpm, + originalCurrency, + width, + height, + latency, + winner: false, + rendered: false, + renderTime: 0, + }; } function onBidTimeout(args) { - utils - ._map(args, value => value) - .forEach(({ auctionId, adUnitCode, bidId }) => { - const bid = - auctionMap[auctionId]['adUnitMap'][adUnitCode]['bidMap'][bidId]; - bid.timedOut = true; - }); + utils._each(args, ({auctionId, adUnitCode, bidId: requestId}) => { + let timedOutRequest = utils.deepAccess(auctionMap, + `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}`); + + if (timedOutRequest) { + timedOutRequest.timedOut = true; + } + }); } +/** + * + * @param {PbAuction} endedAuction + */ +function onAuctionEnd(endedAuction) { + let auction = auctionMap[endedAuction.auctionId]; + + if (!auction) { + return; + } -function onBidWon({ auctionId, adUnitCode, requestId: bidId }) { - const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode]; - const bid = adUnit['bidMap'][bidId]; - bid.won = true; + clearAuctionTimer(auction); + auction.endTime = endedAuction.auctionEnd; + auction.state = AUCTION_STATES.ENDED; + delayedSend(auction); } -function onSlotLoaded({ slot }) { - const targeting = slot.getTargetingKeys().reduce((targeting, key) => { - targeting[key] = slot.getTargeting(key); - return targeting; - }, {}); - utils.logMessage( - 'GPT slot is loaded. Current targeting set on slot:', - targeting - ); +/** + * + * @param {BidResponse} bidResponse + */ +function onBidWon(bidResponse) { + const { auctionId, adUnitCode, requestId, adId } = bidResponse; + let winningBid = utils.deepAccess(auctionMap, + `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}.bids.${adId}`); + + if (winningBid) { + 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}`, + () => { + utils.logInfo(`Openx Analytics - Sending complete impression event for ${adId} at ${Date.now()}`) + }); + } else { + utils.logInfo(`Openx Analytics - impression event for ${adId} will be sent with auction data`) + } + } +} - const adId = slot.getTargeting('hb_adid')[0]; - if (!adId) { - return; +/** + * + * @param {GoogleTagSlot} slot + * @param {string} serviceName + */ +function onSlotLoadedV2({ slot }) { + const renderTime = Date.now(); + const elementId = slot.getSlotElementId(); + const bidId = slot.getTargeting('hb_adid')[0]; + + let [auction, adUnit, bid] = getPathToBidResponseByBidId(bidId); + + if (!auction) { + // attempt to get auction by adUnitCode + auction = getAuctionByGoogleTagSLot(slot); + + if (!auction) { + return; // slot is not participating in an active prebid auction + } } - const adUnit = getAdUnitByAdId(adId); - if (!adUnit) { - return; + clearAuctionTimer(auction); + + // track that an adunit code has completed within an auction + auction.adunitCodesRenderedCount++; + + // mark adunit as rendered + if (bid) { + let {x, y} = getPageOffset(); + bid.rendered = true; + bid.renderTime = renderTime; + adUnit.adPosition = isAtf(elementId, x, y) ? 'ATF' : 'BTF'; } - const adUnitData = getAdUnitData(adUnit); - const performanceData = getPerformanceData(adUnit.auctionStart); - const commonFields = { - 'hb.asiid': slot.getAdUnitPath(), - 'hb.cur': config.getConfig('currency.adServerCurrency'), - 'hb.pubid': initOptions.publisherId - }; + if (auction.adunitCodesRenderedCount === auction.adUnitCodesCount) { + auction.state = AUCTION_STATES.COMPLETED; + } - const data = Object.assign({}, adUnitData, performanceData, commonFields); - sendEvent(data); + // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked + // add additional padding when not all slots are rendered + delayedSend(auction); } -function getAdUnitByAdId(adId) { - let result; +function isAtf(elementId, scrollLeft = 0, scrollTop = 0) { + let elem = document.querySelector('#' + elementId); + let isAtf = false; + if (elem) { + let bounding = elem.getBoundingClientRect(); + if (bounding) { + let windowWidth = (window.innerWidth || document.documentElement.clientWidth); + let windowHeight = (window.innerHeight || document.documentElement.clientHeight); + + // intersection coordinates + let left = Math.max(0, bounding.left + scrollLeft); + let right = Math.min(windowWidth, bounding.right + scrollLeft); + let top = Math.max(0, bounding.top + scrollTop); + let bottom = Math.min(windowHeight, bounding.bottom + scrollTop); + + let intersectionWidth = right - left; + let intersectionHeight = bottom - top; + + let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0; + let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top); + + if (adSlotArea > 0) { + // Atleast 50% of intersection in window + isAtf = intersectionArea * 2 >= adSlotArea; + } + } + } else { + utils.logWarn('OX: DOM element not for id ' + elementId); + } + return isAtf; +} - utils._map(auctionMap, value => value).forEach(auction => { - utils._map(auction.adUnitMap, value => value).forEach(adUnit => { - utils._map(adUnit.bidMap, value => value).forEach(bid => { - if (adId === bid.adId) { - result = adUnit; - } - }) - }); - }); +// backwards compatible pageOffset from https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX +function getPageOffset() { + var x = (window.pageXOffset !== undefined) + ? window.pageXOffset + : (document.documentElement || document.body.parentNode || document.body).scrollLeft; - return result; + var y = (window.pageYOffset !== undefined) + ? window.pageYOffset + : (document.documentElement || document.body.parentNode || document.body).scrollTop; + return {x, y}; } -function getAdUnitData(adUnit) { - const bids = utils._map(adUnit.bidMap, value => value); - const bidders = bids.map(bid => bid.bidder); - const requestTimes = bids.map( - bid => bid.requestTimestamp && bid.requestTimestamp - adUnit.auctionStart - ); - const responseTimes = bids.map( - bid => bid.responseTimestamp && bid.responseTimestamp - adUnit.auctionStart - ); - const bidValues = bids.map(bid => bid.cpm || 0); - const timeouts = bids.map(bid => !!bid.timedOut); - const creativeIds = bids.map(bid => bid.creativeId); - const winningBid = bids.filter(bid => bid.won)[0]; - const winningExchangeIndex = bids.indexOf(winningBid); - const openxBid = bids.filter(bid => bid.bidder === 'openx')[0]; - - return { - 'hb.ct': adUnit.auctionStart, - 'hb.rid': adUnit.auctionId, - 'hb.exn': bidders.join(','), - 'hb.sts': requestTimes.join(','), - 'hb.ets': responseTimes.join(','), - 'hb.bv': bidValues.join(','), - 'hb.to': timeouts.join(','), - 'hb.crid': creativeIds.join(','), - 'hb.we': winningExchangeIndex, - 'hb.g1': winningExchangeIndex === -1, - dddid: adUnit.transactionId, - ts: openxBid && openxBid.ts, - auid: openxBid && openxBid.params && openxBid.params.unit - }; +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(analyticsConfig.endpoint || ENDPOINT, () => { + utils.logInfo(`OpenX Analytics - Sending complete auction at ${Date.now()}`); + }, payload, { contentType: 'application/json' }); + }, delayTime); } -function getPerformanceData(auctionStart) { - let timing; - try { - timing = window.top.performance.timing; - } catch (e) {} +function clearAuctionTimer(auction) { + // reset the delay timer to send the auction data + if (auction.auctionSendDelayTimer) { + clearTimeout(auction.auctionSendDelayTimer); + auction.auctionSendDelayTimer = void (0); + } +} - if (!timing) { - return; +/** + * Returns the path to a bid (auction, adunit, bidRequest, and bid) based on a bidId + * @param {string} bidId + * @returns {Array<*>} + */ +function getPathToBidResponseByBidId(bidId) { + let auction; + let adUnit; + let bidResponse; + + if (!bidId) { + return []; } - const { fetchStart, domContentLoadedEventEnd, loadEventEnd } = timing; - const domContentLoadTime = domContentLoadedEventEnd - fetchStart; - const pageLoadTime = loadEventEnd - fetchStart; - const timeToAuction = auctionStart - fetchStart; - const timeToRender = Date.now() - fetchStart; + utils._each(auctionMap, currentAuction => { + // skip completed auctions + if (currentAuction.state === AUCTION_STATES.COMPLETED) { + return; + } - return { - 'hb.dcl': domContentLoadTime, - 'hb.dl': pageLoadTime, - 'hb.tta': timeToAuction, - 'hb.ttr': timeToRender - }; + utils._each(currentAuction.adUnitCodeToAdUnitMap, (currentAdunit) => { + utils._each(currentAdunit.bidRequestsMap, currentBiddRequest => { + utils._each(currentBiddRequest.bids, (currentBidResponse, bidResponseId) => { + if (bidId === bidResponseId) { + auction = currentAuction; + adUnit = currentAdunit; + bidResponse = currentBidResponse; + } + }); + }); + }); + }); + return [auction, adUnit, bidResponse]; } -function sendEvent(data) { - utils._map(data, (value, key) => [key, value]).forEach(([key, value]) => { - if ( - value === undefined || - value === null || - (typeof value === 'number' && isNaN(value)) - ) { - delete data[key]; +function getAuctionByGoogleTagSLot(slot) { + let slotAdunitCodes = [slot.getSlotElementId(), slot.getAdUnitPath()]; + let slotAuction; + + utils._each(auctionMap, auction => { + if (auction.state === AUCTION_STATES.COMPLETED) { + return; } + + utils._each(auction.adUnitCodeToAdUnitMap, (bidderRequestIdMap, adUnitCode) => { + if (includes(slotAdunitCodes, adUnitCode)) { + slotAuction = auction; + } + }); }); - ajax(ENDPOINT, null, data, { method: 'GET' }); + + return slotAuction; } -let googletag = window.googletag || {}; -googletag.cmd = googletag.cmd || []; -googletag.cmd.push(function() { - googletag.pubads().addEventListener(SLOT_LOADED, args => { - openxAdapter.track({ eventType: SLOT_LOADED, args }); - }); -}); +function buildAuctionPayload(auction) { + let {startTime, endTime, state, timeout, auctionOrder, userIds, adUnitCodeToAdUnitMap, id} = auction; + const auctionId = id; + let {orgId, publisherPlatformId, publisherAccountId, campaign, testCode, configId, optimizerConfig} = analyticsConfig; -const openxAdapter = Object.assign( - adapter({ url: ENDPOINT, analyticsType: 'endpoint' }), - { - track({ eventType, args }) { - utils.logMessage(eventType, Object.assign({}, args)); - switch (eventType) { - case AUCTION_INIT: - onAuctionInit(args); - break; - case BID_REQUESTED: - onBidRequested(args); - break; - case BID_RESPONSE: - onBidResponse(args); - break; - case BID_TIMEOUT: - onBidTimeout(args); - break; - case BID_WON: - onBidWon(args); - break; - case SLOT_LOADED: - onSlotLoaded(args); - break; + return { + auctionId, + adapterVersion: ADAPTER_VERSION, + schemaVersion: SCHEMA_VERSION, + orgId, + publisherPlatformId, + publisherAccountId, + configId, + optimizerConfig, + campaign, + state, + startTime, + endTime, + timeLimit: timeout, + auctionOrder, + deviceType: detectMob() ? 'Mobile' : 'Desktop', + deviceOSType: detectOS(), + browser: detectBrowser(), + 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) => { + let {code, adPosition} = adUnit; + + return { + code, + adPosition, + bidRequests: buildBidRequestPayload(adUnit.bidRequestsMap) + }; + + function buildBidRequestPayload(bidRequestsMap) { + return utils._map(bidRequestsMap, (bidRequest) => { + let {bidder, source, bids, mediaTypes, timeLimit, timedOut} = bidRequest; + return { + bidder, + source, + hasBidderResponded: Object.keys(bids).length > 0, + availableAdSizes: getMediaTypeSizes(mediaTypes), + availableMediaTypes: getMediaTypes(mediaTypes), + timeLimit, + timedOut, + bidResponses: utils._map(bidRequest.bids, (bidderBidResponse) => { + let { + adId, + cpm, + creativeId, + ts, + meta, + mediaType, + dealId, + ttl, + netRevenue, + currency, + width, + height, + latency, + winner, + rendered, + renderTime + } = bidderBidResponse; + + return { + bidId: adId, + microCpm: cpm * 1000000, + netRevenue, + currency, + mediaType, + height, + width, + size: `${width}x${height}`, + dealId, + latency, + ttl, + winner, + creativeId, + ts, + rendered, + renderTime, + meta + } + }) + } + }); } - } + }); } -); - -// save the base class function -openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; -// override enableAnalytics so we can get access to the config passed in from the page -openxAdapter.enableAnalytics = function(config) { - if (!config || !config.options || !config.options.publisherId) { - utils.logError('OpenX analytics adapter: publisherId is required.'); - return; + function buildUserIdProviders(userIds) { + return utils._map(userIds, (userId) => { + return utils._map(userId, (id, module) => { + return hasUserData(module, id) ? module : false + }).filter(module => module); + }).reduce(utils.flatten, []).filter(utils.uniques).sort(); } - initOptions = config.options; - openxAdapter.originEnableAnalytics(config); // call the base class function -}; -// reset the cache for unit tests -openxAdapter.reset = function() { - auctionMap = {}; -}; + function hasUserData(module, idOrIdObject) { + let normalizedId; + + switch (module) { + case 'digitrustid': + normalizedId = utils.deepAccess(idOrIdObject, 'data.id'); + break; + case 'lipb': + normalizedId = idOrIdObject.lipbid; + break; + default: + normalizedId = idOrIdObject; + } -adapterManager.registerAnalyticsAdapter({ - adapter: openxAdapter, - code: 'openx' -}); + return !utils.isEmpty(normalizedId); + } -export default openxAdapter; + function getMediaTypeSizes(mediaTypes) { + return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => { + return utils.parseSizesInput(mediaTypeConfig.sizes) + .map(size => `${mediaType}_${size}`); + }).reduce(utils.flatten, []); + } + + function getMediaTypes(mediaTypes) { + return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType); + } +} diff --git a/modules/openxAnalyticsAdapter.md b/modules/openxAnalyticsAdapter.md index ac739f36c76..af40486f2a4 100644 --- a/modules/openxAnalyticsAdapter.md +++ b/modules/openxAnalyticsAdapter.md @@ -102,11 +102,13 @@ Configuration options are a follows: | Property | Type | Required? | Description | Example | |:---|:---|:---|:---|:---| -| `publisherPlatformId` | `string` | Yes | Used to determine ownership of data. | `a3aece0c-9e80-4316-8deb-faf804779bd1` | -| `publisherAccountId` | `number` | Yes | Used to determine ownership of data. | `1537143056` | +| `orgId` | `string` | Yes | Used to determine ownership of data. | `aa1bb2cc-3dd4-4316-8deb-faf804779bd1` | +| `publisherPlatformId` | `string` | No
**__Deprecated. Please use orgId__** | Used to determine ownership of data. | `a3aece0c-9e80-4316-8deb-faf804779bd1` | +| `publisherAccountId` | `number` | No
**__Deprecated. Please use orgId__** | Used to determine ownership of data. | `1537143056` | | `sampling` | `number` | Yes | Sampling rate | Undefined or `1.00` - No sampling. Analytics is sent all the time.
0.5 - 50% of users will send analytics data. | | `testCode` | `string` | No | Used to label analytics data for the purposes of tests.
This label is treated as a dimension and can be compared against other labels. | `timeout_config_1`
`timeout_config_2`
`timeout_default` | - +| `campaign` | `Object` | No | Object with 5 parameters:
  • content
  • medium
  • name
  • source
  • term
Each parameter is a free-form string. Refer to metrics doc on when to use these fields. By setting a value to one of these properties, you override the associated url utm query parameter. | | +| `payloadWaitTime` | `number` | No | Delay after all slots of an auction renders before the payload is sent.
Defaults to 100ms | 1000 | --- # Viewing Data @@ -114,7 +116,7 @@ The Prebid Report available in the Reporting in the Cloud tool, allows you to vi **To view your data:** -1. Log in to Reporting in the Cloud. +1. Log in to [OpenX Reporting](https://openx.sigmoid.io/app). 2. In the top right, click on the **View** list and then select **Prebidreport**. diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index d5630c2fad4..c3f3a7ddf42 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,23 +2,47 @@ 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.2'; +const BIDDER_VERSION = '3.0.3'; + +const DEFAULT_CURRENCY = 'USD'; export const USER_ID_CODE_TO_QUERY_ARG = { britepoolid: 'britepoolid', // BritePool ID criteoId: 'criteoid', // CriteoID - digitrustid: 'digitrustid', // DigiTrust + fabrickId: 'nuestarid', // Fabrick ID by Nuestar + haloId: 'audigentid', // Halo ID from Audigent id5id: 'id5id', // ID5 ID idl_env: 'lre', // LiveRamp IdentityLink + IDP: 'zeotapid', // zeotapIdPlus ID+ + idxId: 'idxid', // idIDx, + intentIqId: 'intentiqid', // IntentIQ ID lipb: 'lipbid', // LiveIntent ID + lotamePanoramaId: 'lotameid', // Lotame Panorama ID + merkleId: 'merkleid', // Merkle ID netId: 'netid', // netID - parrableid: 'parrableid', // Parrable ID + parrableId: 'parrableid', // Parrable ID pubcid: 'pubcid', // PubCommon ID + quantcastId: 'quantcastid', // Quantcast ID + tapadId: 'tapadid', // Tapad Id tdid: 'ttduuid', // The Trade Desk Unified ID + 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 }; export const spec = { @@ -145,6 +169,12 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) { bidResponse.meta.brandId = adUnit.brand_id; } + if (adUnit.adomain && length(adUnit.adomain) > 0) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } else { + bidResponse.meta.advertiserDomains = []; + } + if (adUnit.adv_id) { bidResponse.meta.dspid = adUnit.adv_id; } @@ -226,6 +256,29 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; + const firstPartyData = config.getConfig('ortb2.user.data') + if (Array.isArray(firstPartyData) && firstPartyData.length > 0) { + // extract and merge valid segments by provider/taxonomy + 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; + }, {}) + const sm = Object.keys(fpd) + .map((name, _) => name + ':' + fpd[name].join('|')) + .join(',') + if (sm.length > 0) { + defaultParams.sm = encodeURIComponent(sm); + } + } + if (bids[0].params.platform) { defaultParams.ph = bids[0].params.platform; } @@ -270,11 +323,27 @@ function appendUserIdsToQueryParams(queryParams, userIds) { if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { switch (userIdProviderKey) { - case 'digitrustid': - queryParams[key] = utils.deepAccess(userIdObjectOrValue, 'data.id'); + 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 + encodeURIComponent(',') : ''}${encodeURIComponent(liveIntentSegments)}`; + } + break; + case 'parrableId': + queryParams[key] = userIdObjectOrValue.eid; + break; + case 'id5id': + queryParams[key] = userIdObjectOrValue.uid; + break; + case 'novatiq': + queryParams[key] = userIdObjectOrValue.snowflake; break; default: queryParams[key] = userIdObjectOrValue; @@ -305,7 +374,12 @@ function buildOXBannerRequest(bids, bidderRequest) { let auids = utils._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.divids = utils._map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); + // gpid + queryParams.aucs = utils._map(bids, function (bid) { + let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + return encodeURIComponent(gpid || '') + }).join(','); if (auids.some(auid => auid)) { queryParams.auid = auids.join(','); @@ -333,19 +407,7 @@ function buildOXBannerRequest(bids, bidderRequest) { queryParams.tps = customParamsForAllBids.join(','); } - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - if (bid.params.customFloor) { - customFloorsForAllBids.push((Math.round(bid.params.customFloor * 100) / 100) * 1000); - hasCustomFloor = true; - } else { - customFloorsForAllBids.push(0); - } - }); - if (hasCustomFloor) { - queryParams.aumfs = customFloorsForAllBids.join(','); - } + enrichQueryWithFloors(queryParams, BANNER, bids); let url = queryParams.ph ? `https://u.openx.net/w/1.0/arj` @@ -373,6 +435,7 @@ function buildOXVideoRequest(bid, bidderRequest) { } function generateVideoParameters(bid, bidderRequest) { + const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); let oxVideoConfig = utils.deepAccess(bid, 'params.video') || {}; let context = utils.deepAccess(bid, 'mediaTypes.video.context'); @@ -392,16 +455,35 @@ function generateVideoParameters(bid, bidderRequest) { 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 (utils.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 @@ -416,6 +498,18 @@ function generateVideoParameters(bid, bidderRequest) { queryParams.vmimes = oxVideoConfig.mimes; } + if (bid.params.test) { + queryParams.vtest = 1; + } + + let gpid = utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + queryParams.aucs = encodeURIComponent(gpid) + } + + // each video bid makes a separate request + enrichQueryWithFloors(queryParams, VIDEO, [bid]); + return queryParams; } @@ -426,6 +520,9 @@ function createVideoBidResponses(response, {bid, startTime}) { let vastQueryParams = utils.parseUrl(response.vastUrl).search || {}; let bidResponse = {}; bidResponse.requestId = bid.bidId; + if (response.deal_id) { + bidResponse.dealId = response.deal_id; + } // default 5 mins bidResponse.ttl = 300; // true is net, false is gross @@ -449,4 +546,38 @@ function createVideoBidResponses(response, {bid, startTime}) { return bidResponses; } +function enrichQueryWithFloors(queryParams, mediaType, bids) { + let customFloorsForAllBids = []; + let hasCustomFloor = false; + bids.forEach(function (bid) { + let floor = getBidFloor(bid, mediaType); + + if (floor) { + customFloorsForAllBids.push(floor); + hasCustomFloor = true; + } else { + customFloorsForAllBids.push(0); + } + }); + if (hasCustomFloor) { + queryParams.aumfs = customFloorsForAllBids.join(','); + } +} + +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: currency, + mediaType: mediaType, + size: '*' + }); + } + let floor = floorInfo.floor || bidRequest.params.customFloor || 0; + + return Math.round(floor * 1000); // normalize to micro currency +} + registerBidder(spec); 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..6b7115b61d0 --- /dev/null +++ b/modules/operaadsBidAdapter.js @@ -0,0 +1,803 @@ +import * as utils 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 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) { + utils.logWarn(BIDDER_CODE, 'Invalid bid,', bid); + return false; + } + + if (!bid.params) { + utils.logWarn(BIDDER_CODE, 'bid.params is required.') + return false; + } + + if (!bid.params.placementId) { + utils.logWarn(BIDDER_CODE, 'bid.params.placementId is required.') + return false; + } + + if (!bid.params.endpointId) { + utils.logWarn(BIDDER_CODE, 'bid.params.endpointId is required.') + return false; + } + + if (!bid.params.publisherId) { + utils.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 && utils.isArray(serverBody.seatbid)) { + serverBody.seatbid.forEach((seatbidder) => { + if (seatbidder.bid && utils.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) { + 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 || !utils.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; + } + + utils.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 = utils.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(utils.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: { + id: getUserId(bidRequest) + } + } + + const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + utils.deepSetValue(payload, 'regs.ext.gdpr', 1); + utils.deepSetValue(payload, 'user.ext.consent', gdprConsent.consentString); + } + + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', uspConsent); + } + + const eids = utils.deepAccess(bidRequest, 'userIdAsEids', []); + if (eids.length > 0) { + utils.deepSetValue(payload, 'user.eids', eids); + } + + return { + method: 'POST', + url: ENDPOINT + String(utils.deepAccess(bidRequest, 'params.publisherId')) + + '?ep=' + String(utils.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 && utils.isPlainObject(markup.native)) { + mediaType = NATIVE; + nativeResponse = markup.native; + } + } + + const currency = responseBody.cur || DEFAULT_CURRENCY; + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + + const categories = utils.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 && utils.isArray(bid.adomain) && bid.adomain.length > 0) { + bidResponse.meta.advertiserDomains = bid.adomain; + bidResponse.meta.clickUrl = bid.adomain[0]; + } + + switch (mediaType) { + case VIDEO: { + const playerSize = utils.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 = utils.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 = utils.deepAccess(nativeResponse, 'link.url'); + if (clickUrl && utils.isStr(clickUrl)) { + native.clickUrl = decodeURIComponent(clickUrl); + } + + const clickTrackers = utils.deepAccess(nativeResponse, 'link.clicktrackers'); + if (clickTrackers && utils.isArray(clickTrackers)) { + native.clickTrackers = clickTrackers + .filter(Boolean) + .map( + url => decodeURIComponent(url) + .replace(/\$\{AUCTION_PRICE\}/g, cpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, currency) + ); + } + + if (nativeResponse.imptrackers && utils.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 && utils.isStr(nativeResponse.jstracker)) { + native.javascriptTrackers = [nativeResponse.jstracker]; + } + + let assets; + if ((assets = nativeResponse.assets) && utils.isArray(assets)) { + assets.forEach((asset) => { + switch (asset.id) { + case NATIVE_DEFAULTS.ASSET_ID.TITLE: { + const title = utils.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 = utils.deepAccess(asset, 'data.value'); + if (body) { + native.body = body; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.SPONSORED: { + const sponsoredBy = utils.deepAccess(asset, 'data.value'); + if (sponsoredBy) { + native.sponsoredBy = sponsoredBy; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.CTA: { + const cta = utils.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(utils.deepAccess(bidRequest, 'params.placementId')), + }; + + let mediaType, size; + let bannerReq, videoReq, nativeReq; + + if ((bannerReq = utils.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 = utils.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 = utils.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 && !utils.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 = utils.deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + return sharedId; + } + + for (const idModule of ['pubcid', 'tdid']) { + let userId = utils.deepAccess(bidRequest, `userId.${idModule}`); + if (userId) { + return userId; + } + } + + return utils.generateUUID(); +} + +/** + * Get publisher domain + * + * @param {String} referer + * @returns {String} domain + */ +function getDomain(referer) { + let domain; + + if (!(domain = config.getConfig('publisherDomain'))) { + const u = utils.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 (utils.isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, + size + }); + + if (utils.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 = utils.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 : (utils.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 = utils.deepAccess(bidRequest, 'renderer'); + const currentRenderer = utils.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) { + utils.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..1e439b28c94 --- /dev/null +++ b/modules/optimeraRtdProvider.js @@ -0,0 +1,220 @@ +/** + * 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 * as utils 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() { + utils.logInfo('Fetch Optimera score file.'); + const ajax = ajaxBuilder(); + ajax(scoresURL, + { + success: (res, req) => { + if (req.status === 200) { + try { + setScores(res); + } catch (err) { + utils.logError('Unable to parse Optimera Score File.', err); + } + } else if (req.status === 403) { + utils.logError('Unable to fetch the Optimera Score File - 403'); + } + }, + error: () => { + utils.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) { + utils.logError('error', err); + } + utils.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) { + utils.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 divice if given. + * @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]; + } + } catch (e) { + utils.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/optimonAnalyticsAdapter.js b/modules/optimonAnalyticsAdapter.js new file mode 100644 index 00000000000..34b2778afc9 --- /dev/null +++ b/modules/optimonAnalyticsAdapter.js @@ -0,0 +1,25 @@ +/** +* +********************************************************* +* +* Optimon.io Prebid Analytics Adapter +* +********************************************************* +* +*/ + +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const optimonAnalyticsAdapter = adapter({ + global: 'OptimonAnalyticsAdapter', + handler: 'on', + analyticsType: 'bundle' +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: optimonAnalyticsAdapter, + code: 'optimon', +}); + +export default optimonAnalyticsAdapter; diff --git a/modules/optimonAnalyticsAdapter.md b/modules/optimonAnalyticsAdapter.md new file mode 100644 index 00000000000..4e2c00dfcab --- /dev/null +++ b/modules/optimonAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Optimon.io Prebid Analytics Adapter +Module Type: Analytics Adapter +Maintainer: hello@optimon.io + +# Description + +Start analyzing your Prebid performance by visiting our website [Optimon.io](https://optimon.io/?utm_source=prebid-org&utm_medium=analytics-adapter) or contact us directly by email: [hello@optimon.io](mailto:hello@optimon.io) to get started. + +# Platform Details + +[Optimon.io](https://optimon.io/?utm_source=prebid-org&utm_medium=analytics-adapter) is a Robust Alerting & Reporting Platform for Prebid and GAM that helps publishers make the right decisions by collecting data from your Google Ad Manager, Prebid, and other SSPs and providing smart insights and suggestions to optimize and maximize their overall yield and save manual work. diff --git a/modules/optoutBidAdapter.js b/modules/optoutBidAdapter.js new file mode 100644 index 00000000000..3a2e672013c --- /dev/null +++ b/modules/optoutBidAdapter.js @@ -0,0 +1,85 @@ +import * as utils 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 utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || utils.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 = !!(utils.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 d14e2bebd72..edd44385d52 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -1,36 +1,94 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; -const storage = getStorageManager(); +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', - orbidderHost: (() => { - let ret = 'https://orbidder.otto.de'; + 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 { - ret = storage.getDataFromLocalStorage('ov_orbidder_host') || ret; + ret = storageManager.getDataFromLocalStorage('ov_orbidder_host') || ret; } catch (e) { } 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) => { let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { referer = bidderRequest.refererInfo.referer || ''; } - const ret = { - url: `${spec.orbidderHost}/bid`, + bidRequest.params.bidfloor = getBidFloor(bidRequest); + + let httpReq = { + url: `${hostname}/bid`, method: 'POST', options: { withCredentials: true }, data: { @@ -41,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 (!utils.isFn(bid.getFloor)) { + return bid.params.bidfloor; + } + + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + if (utils.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 deleted file mode 100644 index 23f6d434ae1..00000000000 --- a/modules/otmBidAdapter.js +++ /dev/null @@ -1,95 +0,0 @@ -import {BANNER} from '../src/mediaTypes.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -export const spec = { - code: 'otm', - supportedMediaTypes: [BANNER], - 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} - }); - - return requests; - }, - interpretResponse: function (serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body) { - return []; - } - - const answer = []; - - 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; - }, -}; - -function getTz() { - return new Date().getTimezoneOffset(); -} - -function getMaxPrioritySize(sizes) { - var maxPrioritySize = null; - - const sizesByPriority = [ - [300, 250], - [240, 400], - [728, 90], - [300, 600], - [970, 250], - [300, 50], - [320, 100] - ]; - - const sizeToString = (size) => { - return size[0] + 'x' + size[1]; - }; - - const sizesAsString = sizes.map(sizeToString); - - sizesByPriority.forEach(size => { - if (!maxPrioritySize) { - if (sizesAsString.indexOf(sizeToString(size)) !== -1) { - maxPrioritySize = size; - } - } - }); - - if (maxPrioritySize) { - return maxPrioritySize; - } else { - return sizes[0]; - } -} - -registerBidder(spec); diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js new file mode 100644 index 00000000000..052122e95f1 --- /dev/null +++ b/modules/outbrainBidAdapter.js @@ -0,0 +1,288 @@ +// 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 = 'outbrain'; +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, + supportedMediaTypes: [ NATIVE, BANNER ], + isBidRequestValid: (bid) => { + return ( + !!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('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, + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + } + }; + + 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('outbrain.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)); + } + + syncs.push({ + type: 'image', + url: syncUrl + (query.length ? '?' + query.join('&') : '') + }); + } + 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/outbrainBidAdapter.md b/modules/outbrainBidAdapter.md new file mode 100644 index 00000000000..32df22ddad8 --- /dev/null +++ b/modules/outbrainBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: Outbrain Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to Outbrain bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Outbrain adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + } + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'outbrain', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + }, + }] + ]; + + pbjs.setConfig({ + outbrain: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` 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 87f140556d0..974fe3a4304 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -5,108 +5,157 @@ import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'ozone'; -const ALLOWED_LOTAME_PARAMS = ['oz_lotameid', 'oz_lotamepid', 'oz_lotametpid']; // *** PROD *** -const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; -const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; +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.4.0'; +const OZONEVERSION = '2.6.0'; export const spec = { + gvlid: 524, + 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: {'lotameWasOverridden': 0, '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', + 'keyPrefix': 'oz', + 'auctionUrl': ORIGIN + AUCTIONURI, + 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, + 'rendererUrl': OZONE_RENDERER_URL + }, + /** + * make sure that the whitelabel/default values are available in the propertyBag + * @param bid Object : the bid + */ + loadWhitelabelData(bid) { + if (this.propertyBag.whitelabel) { return; } + this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); + let bidder = bid.bidder || 'ozone'; // eg. ozone + this.propertyBag.whitelabel.logId = bidder.toUpperCase(); + this.propertyBag.whitelabel.bidder = bidder; + let bidderConfig = config.getConfig(bidder) || {}; + utils.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 (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; + } + } + try { + if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { + utils.logInfo('GET: auction=dev'); + this.propertyBag.whitelabel.auctionUrl = ORIGIN_DEV + AUCTIONURI; + } + if (arr.hasOwnProperty('cookiesync') && arr.cookiesync === 'dev') { + utils.logInfo('GET: cookiesync=dev'); + this.propertyBag.whitelabel.cookieSyncUrl = ORIGIN_DEV + OZONECOOKIESYNC; + } + } catch (e) {} + utils.logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); + }, + getAuctionUrl() { + return this.propertyBag.whitelabel.auctionUrl; + }, + getCookieSyncUrl() { + return this.propertyBag.whitelabel.cookieSyncUrl; + }, + getRendererUrl() { + return this.propertyBag.whitelabel.rendererUrl; + }, /** * Basic check to see whether required parameters are in the request. * @param bid * @returns {boolean} */ isBidRequestValid(bid) { - utils.logInfo('OZONE: isBidRequestValid : ', config.getConfig(), bid); + this.loadWhitelabelData(bid); + utils.logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + utils.logError('VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + utils.logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + utils.logError('VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); + utils.logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + utils.logError('VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + utils.logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); return false; } if (bid.params.hasOwnProperty('customParams')) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + utils.logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); return false; } if (bid.params.hasOwnProperty('customData')) { if (!Array.isArray(bid.params.customData)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an Array', adUnitCode); + utils.logError('VALIDATION FAILED : customData is not an Array', adUnitCode); return false; } if (bid.params.customData.length < 1) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + utils.logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); return false; } if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + utils.logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); return false; } if (typeof bid.params.customData[0]['targeting'] != 'object') { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); - return false; - } - } - if (bid.params.hasOwnProperty('lotameData')) { - if (typeof bid.params.lotameData !== 'object') { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object', adUnitCode); + utils.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')) { - utils.logError('OZONE: No video context key/value in bid. Rejecting bid: ', bid); + utils.logError('No video context key/value in bid. Rejecting bid: ', bid); return false; } if (bid.mediaTypes[VIDEO].context !== 'instream' && bid.mediaTypes[VIDEO].context !== 'outstream') { - utils.logError('OZONE: video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); + utils.logError('video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); return false; } } - // guard against hacks in GET parameters that we might allow - const arrLotameOverride = this.getLotameOverrideParams(); - // lotame override, test params. All 3 must be present, or none. - let lotameKeys = Object.keys(arrLotameOverride); - if (lotameKeys.length === ALLOWED_LOTAME_PARAMS.length) { - utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); - for (let i in lotameKeys) { - if (!arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString().match(/^[0-9a-zA-Z]+$/)) { - utils.logError('OZONE: Only letters & numbers allowed in lotame override: ' + i.toString() + ': ' + arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString() + '. Rejecting bid: ', bid); - return false; - } - } - } else if (lotameKeys.length > 0) { - utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); - utils.logError('OZONE: lotame override params are incomplete. You must set all ' + ALLOWED_LOTAME_PARAMS.length + ': ' + JSON.stringify(ALLOWED_LOTAME_PARAMS) + ', . Rejecting bid: ', bid); - return false; - } return true; }, @@ -119,8 +168,11 @@ export const spec = { }, buildRequests(validBidRequests, bidderRequest) { + this.loadWhitelabelData(validBidRequests[0]); this.propertyBag.buildRequestsStart = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests time: ${this.propertyBag.buildRequestsStart} ozone v ${OZONEVERSION} validBidRequests`, validBidRequests, 'bidderRequest', bidderRequest); + let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; + utils.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? if (this.blockTheRequest()) { return []; @@ -132,31 +184,25 @@ export const spec = { this.cookieSyncBag.publisherId = utils.deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; } - utils.logInfo('OZONE: cookie sync bag', this.cookieSyncBag); - let singleRequest = config.getConfig('ozone.singleRequest'); + utils.logInfo('cookie sync bag', this.cookieSyncBag); + let singleRequest = this.getWhitelabelConfigItem('ozone.singleRequest'); singleRequest = singleRequest !== false; // undefined & true will be true - utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); + utils.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) { - utils.logInfo('OZONE: ADDING GDPR info'); - let apiVersion = utils.deepAccess(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 { - utils.logInfo('OZONE: **** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); - } - } else { - utils.logInfo('OZONE: WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object was present.'); + // First party data module : look for ortb2 in setconfig & set the User object. NOTE THAT this should happen before we set the consentString + let fpd = config.getConfig('ortb2'); + if (fpd && utils.deepAccess(fpd, 'user')) { + utils.logInfo('added FPD user object'); + ozoneRequest.user = fpd.user; } + const getParams = this.getGetParametersAsObject(); - const ozTestMode = getParams.hasOwnProperty('oztestmode') ? getParams.oztestmode : null; // this can be any string, it's used for testing ads + 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 - let lotameDataSingle = {}; // we will capture lotame data once & send it to the server as ext.ozone.lotameData // build the array of params to attach to `imp` let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; @@ -168,18 +214,18 @@ export const spec = { let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); + utils.logInfo('no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - utils.logInfo('OZONE: no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + utils.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 */ - utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + utils.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { - utils.logInfo('OZONE: openrtb 2.5 compliant video'); + utils.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 if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { let childConfig = utils.deepAccess(ozoneBidRequest, 'params.video', {}); @@ -188,25 +234,33 @@ export const spec = { } // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); - utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); + utils.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. - utils.logInfo('OZONE: setting obj.video.format to be an array of objects'); + utils.logInfo('setting obj.video.format to be an array of objects'); obj.video.ext.format = [wh]; } else { - utils.logInfo('OZONE: setting obj.video.format to be an object'); + utils.logInfo('setting obj.video.format to be an object'); obj.video.ext.format = wh; } } else { - utils.logWarn('OZONE: cannot set w, h & format values for video; the config is not right'); + utils.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]; - utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + utils.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + } + // is the publisher specifying floors, and is the floors module enabled? + if (ozoneBidRequest.hasOwnProperty('getFloor')) { + utils.logInfo('This bidRequest object has property: getFloor'); + obj.floor = this.getFloorObjectForAuction(ozoneBidRequest); + utils.logInfo('obj.floor is : ', obj.floor); + } else { + utils.logInfo('This bidRequest object DOES NOT have property: getFloor'); } } if (arrBannerSizes.length > 0) { @@ -222,54 +276,78 @@ 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}}, 'ozone': {}}; - obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' - obj.ext.ozone.transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + // build the imp['ext'] object - NOTE - Dont obliterate anything that' already in obj.ext + utils.deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); + // 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.ozone.customData = ozoneBidRequest.params.customData; + obj.ext[whitelabelBidder].customData = ozoneBidRequest.params.customData; } - utils.logInfo('OZONE: obj.ext.ozone is ', obj.ext.ozone); - if (ozTestMode != null) { - utils.logInfo('OZONE: setting ozTestMode to ', ozTestMode); - if (obj.ext.ozone.hasOwnProperty('customData')) { - for (let i = 0; i < obj.ext.ozone.customData.length; i++) { - obj.ext.ozone.customData[i]['targeting']['oztestmode'] = ozTestMode; + utils.logInfo(`obj.ext.${whitelabelBidder} is `, obj.ext[whitelabelBidder]); + if (isTestMode != null) { + utils.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; } } else { - obj.ext.ozone.customData = [{'settings': {}, 'targeting': {'oztestmode': ozTestMode}}]; + obj.ext[whitelabelBidder].customData = [{'settings': {}, 'targeting': {}}]; + obj.ext[whitelabelBidder].customData[0].targeting[wlOztestmodeKey] = isTestMode; } - } else { - utils.logInfo('OZONE: no ozTestMode '); } - // now deal with lotame, including the optional override parameters - if (Object.keys(lotameDataSingle).length === 0) { // we've not yet found lotameData, see if we can get it from this bid request object - lotameDataSingle = this.tryGetLotameData(ozoneBidRequest); + if (fpd && utils.deepAccess(fpd, 'site')) { + // attach the site fpd into exactly : imp[n].ext.[whitelabel].customData.0.targeting + utils.logInfo('added FPD site object'); + if (utils.deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { + obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); + // let keys = utils.getKeys(fpd.site); + // for (let i = 0; i < keys.length; i++) { + // obj.ext[whitelabelBidder].customData[0].targeting[keys[i]] = fpd.site[keys[i]]; + // } + } else { + utils.deepSetValue(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', fpd.site); + } } return obj; }); // in v 2.0.0 we moved these outside of the individual ad slots - let extObj = {'ozone': {'oz_pb_v': OZONEVERSION, 'oz_rw': placementIdOverrideFromGetParam ? 1 : 0, 'oz_lot_rw': this.propertyBag.lotameWasOverridden}}; + let extObj = {}; + extObj[whitelabelBidder] = {}; + extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; + extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { - let userIds = this.findAllUserIds(validBidRequests[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.ozone.pubcid = userIds.pubcid; + extObj[whitelabelBidder].pubcid = userIds.pubcid; } } - extObj.ozone.pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called - extObj.ozone.lotameData = lotameDataSingle; // 2.4.0 moved lotameData out of bid objects into the single ext.ozone area to remove duplication - let ozOmpFloorDollars = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') - utils.logInfo('OZONE: oz_omp_floor dollar value = ', ozOmpFloorDollars); + 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') + utils.logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); if (typeof ozOmpFloorDollars === 'number') { - extObj.ozone.oz_omp_floor = ozOmpFloorDollars; + extObj[whitelabelBidder][whitelabelPrefix + '_omp_floor'] = ozOmpFloorDollars; } else if (typeof ozOmpFloorDollars !== 'undefined') { - utils.logError('OZONE: oz_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. oz_omp_floor: 1.55. You have it set as a ' + (typeof ozOmpFloorDollars)); + utils.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 = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; - extObj.ozone.oz_kvp_rw = useOzWhitelistAdserverKeys ? 1 : 0; + extObj[whitelabelBidder][whitelabelPrefix + '_kvp_rw'] = useOzWhitelistAdserverKeys ? 1 : 0; + if (whitelabelBidder != 'ozone') { + utils.logInfo('setting aliases object'); + extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; + } + // 20210413 - adding a set of GET params to pass to auction + 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; } + // extObj.ortb2 = config.getConfig('ortb2'); // original test location var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module ozoneRequest.site = { @@ -277,7 +355,27 @@ export const spec = { 'page': document.location.href, 'id': htmlParams.siteId }; - ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] == 'true') ? 1 : 0; + ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; + + // this should come as late as possible so it overrides any user.ext.consent value + if (bidderRequest && bidderRequest.gdprConsent) { + utils.logInfo('ADDING GDPR info'); + let apiVersion = utils.deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); + ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; + if (utils.deepAccess(ozoneRequest, 'regs.ext.gdpr')) { + utils.deepSetValue(ozoneRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } else { + utils.logInfo('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); + } + } else { + utils.logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object'); + } + if (bidderRequest && bidderRequest.uspConsent) { + utils.logInfo('ADDING CCPA info'); + utils.deepSetValue(ozoneRequest, 'user.ext.uspConsent', bidderRequest.uspConsent); + } else { + utils.logInfo('WILL NOT ADD CCPA info; no bidderRequest.uspConsent.'); + } // this is for 2.2.1 // coppa compliance @@ -287,7 +385,7 @@ export const spec = { // return the single request object OR the array: if (singleRequest) { - utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); + utils.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; @@ -296,38 +394,71 @@ export const spec = { utils.deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', - url: OZONEURI, + url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); + utils.logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(ozoneRequest))); this.propertyBag.buildRequestsEnd = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + utils.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 => { - utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp); + utils.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); - imp.ext.ozone.pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object - ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. - ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? + 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.ozone.transactionId}; + ozoneRequestSingle.source = {'tid': imp.ext[whitelabelBidder].transactionId}; utils.deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); - utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); + utils.logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', - url: OZONEURI, + url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequestSingle), bidderRequest: bidderRequest }; }); this.propertyBag.buildRequestsEnd = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); + utils.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: utils.deepAccess(bidRequestRef, 'mediaTypes.banner.sizes', null), + video: utils.deepAccess(bidRequestRef, 'mediaTypes.video.playerSize', null), + native: utils.deepAccess(bidRequestRef, 'mediaTypes.native.image.sizes', null) + } + utils.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}); + } + utils.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 @@ -339,9 +470,12 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { + if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadWhitelabelData(request.bidderRequest.bids[0]); } let startTime = new Date().getTime(); - utils.logInfo(`OZONE: interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - utils.logInfo(`OZONE: serverResponse, request`, serverResponse, request); + let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; + utils.logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + utils.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')) { @@ -351,91 +485,102 @@ export const spec = { return []; } let arrAllBids = []; - let enhancedAdserverTargeting = config.getConfig('ozone.enhancedAdserverTargeting'); - utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); + let enhancedAdserverTargeting = this.getWhitelabelConfigItem('ozone.enhancedAdserverTargeting'); + utils.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); if (typeof enhancedAdserverTargeting == 'undefined') { enhancedAdserverTargeting = true; } - utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); + utils.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + + // 2021-03-05 - comment this out for a build without adding adid to the response 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 = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') + let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') let addOzOmpFloorDollars = typeof ozOmpFloorDollars === 'number'; - let ozWhitelistAdserverKeys = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = utils.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); - utils.logInfo(`OZONE seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + utils.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); + // prebid 4.0 compliance + thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; let isVideo = false; let bidType = utils.deepAccess(thisBid, 'ext.prebid.type'); - utils.logInfo(`OZONE: this bid type is : ${bidType}`, j); + utils.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') { - utils.logInfo('OZONE: going to attach a renderer to OUTSTREAM video : ', j); + utils.logInfo('going to attach a renderer to OUTSTREAM video : ', j); thisBid.renderer = newRenderer(thisBid.bidId); } else { - utils.logInfo('OZONE: bid is not an outstream video, will not attach a renderer: ', j); + utils.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: - utils.logInfo('OZONE: Going to iterate allBidsForThisBidId', allBidsForThisBidid); - Object.keys(allBidsForThisBidid).forEach(function (bidderName, index, ar2) { - utils.logInfo(`OZONE: adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); + utils.logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { + utils.logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); // let bidderName = bidderNameWH.split('_')[0]; - adserverTargeting['oz_' + bidderName] = bidderName; - adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); - adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); - adserverTargeting['oz_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); - adserverTargeting['oz_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { - adserverTargeting['oz_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); } if (addOzOmpFloorDollars) { - adserverTargeting['oz_' + bidderName + '_omp'] = allBidsForThisBidid[bidderName].price >= ozOmpFloorDollars ? '1' : '0'; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_omp'] = allBidsForThisBidid[bidderName].price >= ozOmpFloorDollars ? '1' : '0'; } if (isVideo) { - adserverTargeting['oz_' + bidderName + '_vid'] = videoContext; // outstream or instream + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_vid'] = videoContext; // outstream or instream } - let flr = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.floor', null); + let flr = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.floor`, null); if (flr != null) { - adserverTargeting['oz_' + bidderName + '_flr'] = flr; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_flr'] = flr; } - let rid = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.ruleId', null); + let rid = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.ruleId`, null); if (rid != null) { - adserverTargeting['oz_' + bidderName + '_rid'] = rid; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_rid'] = rid; } if (bidderName.match(/^ozappnexus/)) { - adserverTargeting['oz_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); } }); } else { if (useOzWhitelistAdserverKeys) { - utils.logWarn('OZONE: You have set a whitelist of adserver keys but this will be ignored because ozone.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.'); + utils.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 { - utils.logInfo('OZONE: ozone.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.'); + utils.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['oz_auc_id'] = String(request.bidderRequest.auctionId); - adserverTargeting['oz_winner'] = String(winningSeat); + adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); + adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); + adserverTargeting[whitelabelPrefix + '_bid'] = 'true'; + if (enhancedAdserverTargeting) { - adserverTargeting['oz_imp_id'] = String(winningBid.impid); - adserverTargeting['oz_pb_v'] = OZONEVERSION; + 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 - utils.logInfo('OZONE: Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); + utils.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; @@ -443,9 +588,22 @@ export const spec = { } } let endTime = new Date().getTime(); - utils.logInfo(`OZONE: interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + utils.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' + * @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 + '_'); + return config.getConfig(whitelabelledSearch); + }, /** * If a bidder bids for > 1 size for an adslot, allow only the highest bid * @param seatbid object (serverResponse.seatbid) @@ -474,8 +632,9 @@ export const spec = { return ret; }, // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs - getUserSyncs(optionsType, serverResponse, gdprConsent) { - utils.logInfo('OZONE: getUserSyncs optionsType, serverResponse, gdprConsent, cookieSyncBag', optionsType, serverResponse, gdprConsent, this.cookieSyncBag); + // us privacy: https://docs.prebid.org/dev-docs/modules/consentManagementUsp.html + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + utils.logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } @@ -486,23 +645,28 @@ export const spec = { } 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('usp_consent=' + (usPrivacy || '')); + // var objKeys = Object.getOwnPropertyNames(this.cookieSyncBag.userIdObject); + // for (let idx in objKeys) { + // let keyname = objKeys[idx]; + // arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); + // } + for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); arrQueryString.push('cb=' + Date.now()); + arrQueryString.push('bidder=' + this.propertyBag.whitelabel.bidder); var strQueryString = arrQueryString.join('&'); if (strQueryString.length > 0) { strQueryString = '?' + strQueryString; } - utils.logInfo('OZONE: getUserSyncs going to return cookie sync url : ' + OZONECOOKIESYNC + strQueryString); + utils.logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); return [{ type: 'iframe', - url: OZONECOOKIESYNC + strQueryString + url: this.getCookieSyncUrl() + strQueryString }]; } }, @@ -534,95 +698,60 @@ export const spec = { 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 * @return map */ findAllUserIds(bidRequest) { var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableid', 'idl_env', 'digitrustid', 'criteortus']; + // @todo - what is Neustar fabrick called & where to look for it? If it's a simple value then it will automatically be ok + // it is not in the table 'Bidder Adapter Implementation' on https://docs.prebid.org/dev-docs/modules/userId.html#prebidjs-adapters + let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; + if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; if (bidRequest.userId.hasOwnProperty(key)) { - ret[key] = bidRequest.userId[key]; + if (typeof (bidRequest.userId[key]) == 'string') { + ret[key] = bidRequest.userId[key]; + } else if (typeof (bidRequest.userId[key]) == 'object') { + utils.logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); + // fallback - get the value of the first key in the object; this is NOT desirable behaviour + ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values + } else { + utils.logError(`failed to get string key value for userId : ${key}`); + } } } - var lipbid = utils.deepAccess(bidRequest.userId, 'lipb.lipbid'); + let lipbid = utils.deepAccess(bidRequest.userId, 'lipb.lipbid'); if (lipbid) { ret['lipb'] = {'lipbid': lipbid}; } + let id5id = utils.deepAccess(bidRequest.userId, 'id5id.uid'); + if (id5id) { + ret['id5id'] = id5id; + } + let parrableId = utils.deepAccess(bidRequest.userId, 'parrableId.eid'); + if (parrableId) { + ret['parrableId'] = parrableId; + } + let sharedid = utils.deepAccess(bidRequest.userId, 'sharedid.id'); + if (sharedid) { + ret['sharedid'] = sharedid; + } + let sharedidthird = utils.deepAccess(bidRequest.userId, 'sharedid.third'); + if (sharedidthird) { + ret['sharedidthird'] = sharedidthird; + } } if (!ret.hasOwnProperty('pubcid')) { - var pubcid = utils.deepAccess(bidRequest, 'crumbs.pubcid'); + let pubcid = utils.deepAccess(bidRequest, 'crumbs.pubcid'); if (pubcid) { ret['pubcid'] = pubcid; // if built with old pubCommonId module } } return ret; }, - /** - * get all the lotame override keys/values from the querystring. - * @return object containing zero or more keys/values - */ - getLotameOverrideParams() { - const arrGet = this.getGetParametersAsObject(); - utils.logInfo('OZONE: getLotameOverrideParams - arrGet=', arrGet); - let arrRet = {}; - for (let i in ALLOWED_LOTAME_PARAMS) { - if (arrGet.hasOwnProperty(ALLOWED_LOTAME_PARAMS[i])) { - arrRet[ALLOWED_LOTAME_PARAMS[i]] = arrGet[ALLOWED_LOTAME_PARAMS[i]]; - } - } - return arrRet; - }, - /** - * Boolean function to check that this lotame data is valid (check Audience.id) - */ - isLotameDataValid(lotameObj) { - if (!lotameObj.hasOwnProperty('Profile')) return false; - let prof = lotameObj.Profile; - if (!prof.hasOwnProperty('tpid')) return false; - if (!prof.hasOwnProperty('pid')) return false; - let audiences = utils.deepAccess(prof, 'Audiences.Audience'); - if (typeof audiences != 'object') { - return false; - } - for (var i = 0; i < audiences.length; i++) { - let aud = audiences[i]; - if (!aud.hasOwnProperty('id')) { - return false; - } - } - return true; // All Audiences objects have an 'id' key - }, - /** - * Use the arrOverride keys/vals to update the arrExisting lotame object. - * Ideally we will only be using the oz_lotameid value to update the audiences id, but in the event of bad/missing - * pid & tpid we will also have to use substitute values for those too. - * - * @param objOverride object will contain all the ALLOWED_LOTAME_PARAMS parameters - * @param lotameData object might be {} or contain the lotame data - */ - makeLotameObjectFromOverride(objOverride, lotameData) { - if ((lotameData.hasOwnProperty('Profile') && Object.keys(lotameData.Profile).length < 3) || - (!lotameData.hasOwnProperty('Profile'))) { // bad or empty lotame object (should contain pid, tpid & Audiences object) - build a total replacement - utils.logInfo('OZONE: makeLotameObjectFromOverride will return a full default lotame object'); - return { - 'Profile': { - 'tpid': objOverride['oz_lotametpid'], - 'pid': objOverride['oz_lotamepid'], - 'Audiences': {'Audience': [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]} - } - }; - } - if (utils.deepAccess(lotameData, 'Profile.Audiences.Audience')) { - utils.logInfo('OZONE: makeLotameObjectFromOverride will return the existing lotame object with updated Audience by oz_lotameid'); - lotameData.Profile.Audiences.Audience = [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]; - return lotameData; - } - utils.logInfo('OZONE: makeLotameObjectFromOverride Weird error - failed to find Profile.Audiences.Audience in lotame object. Will return the object as-is'); - return lotameData; - }, /** * Convenient method to get the value we need for the placementId - ONLY from the bidRequest - NOT taking into account any GET override ID * @param bidRequest @@ -638,48 +767,30 @@ export const spec = { * @returns null|string */ getPlacementIdOverrideFromGetParam() { + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; let arr = this.getGetParametersAsObject(); - if (arr.hasOwnProperty('ozstoredrequest')) { - if (this.isValidPlacementId(arr.ozstoredrequest)) { - utils.logInfo('OZONE: using GET ozstoredrequest ' + arr.ozstoredrequest + ' to replace placementId'); - return arr.ozstoredrequest; + if (arr.hasOwnProperty(whitelabelPrefix + 'storedrequest')) { + if (this.isValidPlacementId(arr[whitelabelPrefix + 'storedrequest'])) { + utils.logInfo(`using GET ${whitelabelPrefix}storedrequest ` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); + return arr[whitelabelPrefix + 'storedrequest']; } else { - utils.logError('OZONE: GET ozstoredrequest FAILED VALIDATION - will not use it'); + utils.logError(`GET ${whitelabelPrefix}storedrequest FAILED VALIDATION - will not use it`); } } return null; }, - /** - * Produces external userid object - */ - addExternalUserId(eids, value, source, atype) { - if (utils.isStr(value)) { - eids.push({ - source, - uids: [{ - id: value, - atype - }] - }); - } - }, /** * Generate an object we can append to the auction request, containing user data formatted correctly for different ssps + * http://prebid.org/dev-docs/modules/userId.html * @param validBidRequests * @return {Array} */ generateEids(validBidRequests) { - let eids = []; - this.handleTTDId(eids, validBidRequests); + let eids; const bidRequest = validBidRequests[0]; if (bidRequest && bidRequest.userId) { - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableid`), 'parrable.com', 1); + eids = bidRequest.userIdAsEids; + this.handleTTDId(eids, validBidRequests); } return eids; }, @@ -721,9 +832,9 @@ export const spec = { */ blockTheRequest() { // if there is an ozone.oz_request = false then quit now. - let ozRequest = config.getConfig('ozone.oz_request'); + let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { - utils.logWarn('OZONE: Will not allow auction : ozone.oz_request is set to false'); + utils.logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); return true; } return false; @@ -743,34 +854,6 @@ export const spec = { } return this.propertyBag.pageId; }, - /** - * handle the complexity of there possibly being lotameData override (may be valid/invalid) & there may or may not be lotameData present in the bidRequest - * NOTE THAT this will also set this.propertyBag.lotameWasOverridden=1 if we use lotame override - * @param ozoneBidRequest - * @return object representing the absolute lotameData we need to use. - */ - tryGetLotameData: function(ozoneBidRequest) { - const arrLotameOverride = this.getLotameOverrideParams(); - let ret = {}; - if (Object.keys(arrLotameOverride).length === ALLOWED_LOTAME_PARAMS.length) { - // all override params are present, override lotame object: - if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { - ret = this.makeLotameObjectFromOverride(arrLotameOverride, ozoneBidRequest.params.lotameData); - } else { - ret = this.makeLotameObjectFromOverride(arrLotameOverride, {}); - } - this.propertyBag.lotameWasOverridden = 1; - } else if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { - // no lotame override, use it as-is - if (this.isLotameDataValid(ozoneBidRequest.params.lotameData)) { - ret = ozoneBidRequest.params.lotameData; - } else { - utils.logError('OZONE: INVALID LOTAME DATA FOUND - WILL NOT USE THIS AT ALL ELSE IT MIGHT BREAK THE AUCTION CALL!', ozoneBidRequest.params.lotameData); - ret = {}; - } - } - return ret; - }, unpackVideoConfigIntoIABformat(videoConfig, childConfig) { let ret = {'ext': {}}; ret = this._unpackVideoConfigIntoIABformat(ret, videoConfig); @@ -849,13 +932,13 @@ export const spec = { * @returns seatbid object */ export function injectAdIdsIntoAllBidResponses(seatbid) { - utils.logInfo('OZONE: injectAdIdsIntoAllBidResponses', seatbid); + utils.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; @@ -875,7 +958,7 @@ export function checkDeepArray(Arr) { export function defaultSize(thebidObj) { if (!thebidObj) { - utils.logInfo('OZONE: defaultSize received empty bid obj! going to return fixed default size'); + utils.logInfo('defaultSize received empty bid obj! going to return fixed default size'); return { 'defaultHeight': 250, 'defaultWidth': 300 @@ -928,7 +1011,7 @@ 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]; + // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; if (objBids[thisSeat]['price'] < theseBids[k].price) { objBids[thisSeat] = theseBids[k]; } @@ -953,14 +1036,14 @@ export function getRoundedBid(price, mediaType) { let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - utils.logInfo('OZONE: getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); + utils.logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); let priceStringsObj = getPriceBucketString( price, theConfigObject, config.getConfig('currency.granularityMultiplier') ); - utils.logInfo('OZONE: priceStringsObj', priceStringsObj); + utils.logInfo('priceStringsObj', priceStringsObj); // by default, without any custom granularity set, you get granularity name : 'medium' let granularityNamePriceStringsKeyMapping = { 'medium': 'med', @@ -971,7 +1054,7 @@ export function getRoundedBid(price, mediaType) { }; if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - utils.logInfo('OZONE: getRoundedBid: looking for priceStringsKey:', priceStringsKey); + utils.logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); return priceStringsObj[priceStringsKey]; } return priceStringsObj['auto']; @@ -1040,15 +1123,15 @@ export function getWidthAndHeightFromVideoObject(objVideo) { return null; } if (playerSize[0] && typeof playerSize[0] === 'object') { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); + utils.logInfo('getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); playerSize = playerSize[0]; if (typeof playerSize[0] !== 'number' && typeof playerSize[0] !== 'string') { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); + utils.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) { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); + utils.logInfo('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); return null; } return ({'w': playerSize[0], 'h': playerSize[1]}); @@ -1075,17 +1158,17 @@ export function playerSizeIsNestedArray(objVideo) { * @returns {*} */ function getPlayerSizeFromObject(objVideo) { - utils.logInfo('OZONE: getPlayerSizeFromObject received object', objVideo); + utils.logInfo('getPlayerSizeFromObject received object', objVideo); let playerSize = utils.deepAccess(objVideo, 'playerSize'); if (!playerSize) { playerSize = utils.deepAccess(objVideo, 'ext.playerSize'); } if (!playerSize) { - utils.logError('OZONE: getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); + utils.logError('getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); return null; } if (typeof playerSize !== 'object') { - utils.logError('OZONE: getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); + utils.logError('getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); return null; } return playerSize; @@ -1095,21 +1178,23 @@ function getPlayerSizeFromObject(objVideo) { The renderer function will not assume that the renderer script is loaded - it will push() the ultimate render function call */ function newRenderer(adUnitCode, rendererOptions = {}) { + let isLoaded = window.ozoneVideo; + utils.logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); const renderer = Renderer.install({ - url: OZONE_RENDERER_URL, + url: spec.getRendererUrl(), config: rendererOptions, - loaded: false, + loaded: isLoaded, adUnitCode }); try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('OZONE Prebid Error calling setRender on renderer', err); + utils.logError('Prebid Error when calling setRender on renderer', JSON.parse(JSON.stringify(renderer)), err); } return renderer; } function outstreamRender(bid) { - utils.logInfo('OZONE: outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', bid); + utils.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 bid.renderer.push(() => { window.ozoneVideo.outstreamRender(bid); @@ -1117,4 +1202,4 @@ function outstreamRender(bid) { } registerBidder(spec); -utils.logInfo('OZONE: ozoneBidAdapter was loaded'); +utils.logInfo(`*BidAdapter ${OZONEVERSION} was loaded`); diff --git a/modules/ozoneBidAdapter.md b/modules/ozoneBidAdapter.md index bc8cb6a6102..ca18c962219 100644 --- a/modules/ozoneBidAdapter.md +++ b/modules/ozoneBidAdapter.md @@ -37,7 +37,6 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ placementId: '0420420421', /* an ID used to identify the piece of inventory - required - for appnexus test use 13144370. */ customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ } }] }]; @@ -52,7 +51,7 @@ adUnits = [{ code: 'id-of-your-video-div', mediaTypes: { video: { - playerSize: [640, 480], + playerSize: [640, 360], mimes: ['video/mp4'], context: 'outstream', } @@ -64,7 +63,6 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ customData: [{"settings": {}, "targeting": { "key": "value", "key2": ["value1", "value2"]}}] placementId: '0440440442', /* an ID used to identify the piece of inventory - required - for unruly test use 0440440442. */ - lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ video: { skippable: true, /* optional */ playback_method: ['auto_play_sound_off'], /* optional */ 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 ec033e62983..1d74061e544 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -5,7 +5,10 @@ * @requires module:modules/userId */ +// ci trigger: 1 + import * as utils 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'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -14,12 +17,13 @@ import { getStorageManager } from '../src/storageManager.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; +const PARRABLE_GVLID = 928; const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const storage = getStorageManager(); +const storage = getStorageManager(PARRABLE_GVLID); function getExpirationDate() { const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS); @@ -32,25 +36,40 @@ 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(','); } @@ -60,7 +79,7 @@ function isValidConfig(configParams) { utils.logError('User ID - parrableId submodule requires configParams'); return false; } - if (!configParams.partner) { + if (!configParams.partners && !configParams.partner) { utils.logError('User ID - parrableId submodule requires partner list'); return false; } @@ -70,17 +89,41 @@ function isValidConfig(configParams) { return true; } +function encodeBase64UrlSafe(base64) { + const ENC = { + '+': '-', + '/': '_', + '=': '.' + }; + return base64.replace(/[+/=]/g, (m) => ENC[m]); +} + 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'); } } @@ -113,27 +156,107 @@ function migrateLegacyCookies(parrableId) { } } -function fetchId(configParams) { +function shouldFilterImpression(configParams, parrableId) { + const config = configParams.timezoneFilter; + + if (!config) { + return false; + } + + if (parrableId) { + return false; + } + + const offset = (new Date()).getTimezoneOffset() / 60; + const zone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + function isZoneListed(list, zone) { + // IE does not provide a timeZone in IANA format so zone will be empty + const zoneLowercase = zone && zone.toLowerCase(); + return !!(list && zone && find(list, zn => zn.toLowerCase() === zoneLowercase)); + } + + function isAllowed() { + if (utils.isEmpty(config.allowedZones) && + utils.isEmpty(config.allowedOffsets)) { + return true; + } + if (isZoneListed(config.allowedZones, zone)) { + return true; + } + if (utils.contains(config.allowedOffsets, offset)) { + return true; + } + return false; + } + + function isBlocked() { + if (utils.isEmpty(config.blockedZones) && + utils.isEmpty(config.blockedOffsets)) { + return false; + } + if (isZoneListed(config.blockedZones, zone)) { + return true; + } + if (utils.contains(config.blockedOffsets, offset)) { + return true; + } + return false; + } + + 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); } - const eid = (parrableId) ? parrableId.eid : null; + if (shouldFilterImpression(configParams, parrableId)) { + return 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 trackers = typeof partners === 'string' + ? partners.split(',') + : partners; const data = { eid, - trackers: configParams.partner.split(','), - url: refererInfo.referer + trackers, + url: refererInfo.referer, + prebidVersion: '$prebid.version$', + isIframe: utils.inIframe(), + tpcSupport }; + if (shouldFilter === false) { + data.filterHits = params.filterHits; + } + const searchParams = { - data: btoa(JSON.stringify(data)), + data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), + gdpr: gdprApplies ? 1 : 0, _rand: Math.random() }; @@ -141,6 +264,10 @@ function fetchId(configParams) { searchParams.us_privacy = uspString; } + if (gdprApplies) { + searchParams.gdpr_consent = gdprConsentString; + } + const options = { method: 'GET', withCredentials: true @@ -150,6 +277,7 @@ function fetchId(configParams) { const callbacks = { success: response => { let newParrableId = parrableId ? utils.deepClone(parrableId) : {}; + let newParams = {}; if (response) { try { let responseObj = JSON.parse(response); @@ -163,12 +291,20 @@ function fetchId(configParams) { 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); cb(); } - writeCookie(newParrableId); + writeCookie({ ...newParrableId, ...newParams }); cb(newParrableId); } else { utils.logError('parrableId: ID fetch returned an empty result'); @@ -180,14 +316,19 @@ function fetchId(configParams) { cb(); } }; - ajax(PARRABLE_URL, callbacks, searchParams, options); + + if (shouldFilter) { + incrementFilterHits(parrableId, params); + } else { + ajax(PARRABLE_URL, callbacks, searchParams, options); + } }; return { callback, id: parrableId }; -}; +} /** @type {Submodule} */ export const parrableIdSubmodule = { @@ -196,6 +337,12 @@ export const parrableIdSubmodule = { * @type {string} */ name: 'parrableId', + /** + * Global Vendor List ID + * @type {number} + */ + gvlid: PARRABLE_GVLID, + /** * decode the stored id value for passing to bid requests * @function @@ -204,7 +351,7 @@ export const parrableIdSubmodule = { */ decode(parrableId) { if (parrableId && utils.isPlainObject(parrableId)) { - return { 'parrableid': parrableId.eid }; + return { parrableId }; } return undefined; }, @@ -212,12 +359,13 @@ export const parrableIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @returns {function(callback:function), id:ParrableId} */ - getId(configParams, gdprConsentData, currentStoredId) { - return fetchId(configParams); + getId(config, gdprConsentData, currentStoredId) { + const configParams = (config && config.params) || {}; + return fetchId(configParams, gdprConsentData); } }; 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 new file mode 100644 index 00000000000..40282567506 --- /dev/null +++ b/modules/permutiveRtdProvider.js @@ -0,0 +1,280 @@ +/** + * This module adds permutive provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will add custom segment targeting to ad units of specific bidders + * @module modules/permutiveRtdProvider + * @requires module:modules/realTimeData + */ +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 (moduleConfig, userConsent) { + return true +} + +/** + * 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 moduleConfig = getModuleConfig(customModuleConfig) + const segmentData = getSegments(moduleConfig.params.maxSegs) + + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + + if (moduleConfig.waitForIt && permutiveOnPage) { + window.permutive.ready(function () { + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + callback() + }, 'realtime') + } else { + callback() + } +} + +/** + * 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 => { + 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, segmentData, acEnabled, utils, defaultFn) + } else if (defaultFn) { + defaultFn(bid, segmentData, acEnabled) + } + }) + }) +} + +/** + * Catch and log errors + * @param {function} fn - Function to safely evaluate + */ +function makeSafe (fn) { + try { + fn() + } catch (e) { + logError(e) + } +} + +function getCustomBidderFn (moduleConfig, bidder) { + const overwriteFn = deepAccess(moduleConfig, `params.overwrites.${bidder}`) + + if (overwriteFn && isFn(overwriteFn)) { + return overwriteFn + } else { + return null + } +} + +/** + * 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 bidderMap = { + appnexus: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.keywords.p_standard', data.ac) + } + if (data.appnexus && data.appnexus.length) { + deepSetValue(bid, 'params.keywords.permutive', data.appnexus) + } + + return bid + }, + rubicon: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.visitor.p_standard', data.ac) + } + if (data.rubicon && data.rubicon.length) { + deepSetValue(bid, 'params.visitor.permutive', data.rubicon) + } + + return bid + }, + ozone: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) + } + + return bid + } + } + + return bidderMap[bidder] +} + +/** + * 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' +} + +/** + * 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') + const _pcrprs = readSegments('_pcrprs') + + const segments = { + ac: [..._pcrprs, ..._ppam, ...legacySegs], + rubicon: readSegments('_prubicons'), + appnexus: readSegments('_papns'), + gam: readSegments('_pdfps') + } + + for (const type in segments) { + segments[type] = segments[type].slice(0, maxSegs) + } + + return segments +} + +/** + * Gets an array of segment IDs from LocalStorage + * or returns an empty array + * @param {string} key + * @return {string[]|number[]} + */ +function readSegments (key) { + try { + return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + } catch (e) { + return [] + } +} + +/** @type {RtdSubmodule} */ +export const permutiveSubmodule = { + name: MODULE_NAME, + getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + makeSafe(function () { + // Legacy route with custom parameters + initSegments(reqBidsConfigObj, callback, customModuleConfig) + }) + }, + onAuctionInitEvent: function (auctionDetails, customModuleConfig) { + makeSafe(function () { + // Route for bidders supporting ORTB2 + setBidderRtb(auctionDetails, customModuleConfig) + }) + }, + init: init +} + +submodule('realTimeData', permutiveSubmodule) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md new file mode 100644 index 00000000000..0acd42405d1 --- /dev/null +++ b/modules/permutiveRtdProvider.md @@ -0,0 +1,53 @@ +# Permutive Real-time Data Submodule +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=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 +pbjs.setConfig({ + ..., + realTimeData: { + auctionDelay: 50, // optional auction delay + dataProviders: [{ + name: 'permutive', + waitForIt: true, // should be true if there's an `auctionDelay` + params: { + acBidders: ['appnexus'] + } + }] + }, + ... +}) +``` + +## Supported 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 | Custom Cohorts | Audience Connector | +| ----------- | ---------- | -------------------- | ------------------ | +| Xandr | `appnexus` | Yes | Yes | +| Magnite | `rubicon` | Yes | No | +| Ozone | `ozone` | No | Yes | + +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 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 +| Name | Type | Description | Default | +| ----------------- | -------------------- | ------------------ | ------------------ | +| 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 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/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js new file mode 100644 index 00000000000..e5422f36358 --- /dev/null +++ b/modules/pixfutureBidAdapter.js @@ -0,0 +1,323 @@ +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 * as utils 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://prebid-js.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 = utils.convertCamelToUnderscore(param); + if (param === 'segments' && utils.isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (utils.isNumber(val)) { + segs.push({'id': val}); + } else if (utils.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, utils.deepAccess(validBidRequests[0], `userId.flocId.id`), 'chrome.com', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.criteoId`), 'criteo.com', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.unifiedId`), 'thetradedesk.com', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.id5Id`), 'id5.io', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.sharedId`), 'thetradedesk.com', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.identityLink`), 'liveramp.com', null); + addUserId(eids, utils.deepAccess(validBidRequests[0], `userId.liveIntentId`), 'liveintent.com', null); + addUserId(eids, utils.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}/`, + 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; + }, +}; + +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; +} + +// 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 (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = utils.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 && utils.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 (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 getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : 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 deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +registerBidder(spec); diff --git a/modules/pixfutureBidAdapter.md b/modules/pixfutureBidAdapter.md new file mode 100644 index 00000000000..e5728ededc6 --- /dev/null +++ b/modules/pixfutureBidAdapter.md @@ -0,0 +1,27 @@ +# 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" + }]; +``` 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/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 56e4fdfbfef..f6b8ac9f86a 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -3,15 +3,49 @@ export const S2S_VENDORS = { 'appnexus': { adapter: 'prebidServer', enabled: true, - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', - syncEndpoint: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction' + }, + syncEndpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + }, + 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, - endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', + endpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + }, + syncEndpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + }, timeout: 500 + }, + 'openx': { + adapter: 'prebidServer', + enabled: true, + endpoint: { + p1Consent: 'https://prebid.openx.net/openrtb2/auction', + noP1Consent: 'https://prebid.openx.net/openrtb2/auction' + }, + syncEndpoint: { + p1Consent: 'https://prebid.openx.net/cookie_sync', + noP1Consent: 'https://prebid.openx.net/cookie_sync' + }, + timeout: 1000 } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a5dcf1b1d3b..d7caf0dbad8 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,7 +1,7 @@ import Adapter from '../../src/adapter.js'; import { createBid } from '../../src/bidfactory.js'; import * as utils from '../../src/utils.js'; -import { STATUS, S2S, EVENTS } from '../../src/constants.json'; +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'; @@ -12,20 +12,28 @@ 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'; const getConfig = config.getConfig; -const TYPE = S2S.SRC; -let _synced = false; +const TYPE = CONSTANTS.S2S.SRC; +let _syncCount = 0; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; const DEFAULT_S2S_NETREVENUE = true; -let _s2sConfig; +let _s2sConfigs; + +let eidPermissions; /** * @typedef {Object} AdapterOptions * @summary s2sConfig parameter that adds arguments to resulting OpenRTB payload that goes to Prebid Server + * @property {string} adapter + * @property {boolean} enabled + * @property {string} endpoint + * @property {string} syncEndpoint + * @property {number} timeout * @example * // example of multiple bidder configuration * pbjs.setConfig({ @@ -40,18 +48,30 @@ let _s2sConfig; /** * @typedef {Object} S2SDefaultConfig - * @property {boolean} enabled - * @property {number} timeout - * @property {number} maxBids - * @property {string} adapter - * @property {AdapterOptions} adapterOptions + * @summary Base config properties for server to server header bidding + * @property {string} [adapter='prebidServer'] adapter code to use for S2S + * @property {boolean} [enabled=false] enables S2S bidding + * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [maxBids=1] + * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server + * @property {Object} [syncUrlModifier] + */ + +/** + * @typedef {S2SDefaultConfig} S2SConfig + * @summary Configuration for server to server header bidding + * @property {string[]} bidders bidders to request S2S + * @property {string} endpoint endpoint to contact + * @property {string} [defaultVendor] used as key to select the bidder's default config from ßprebidServer/config.js + * @property {boolean} [cacheMarkup] whether to cache the adm result + * @property {string} [syncEndpoint] endpoint URL for syncing cookies + * @property {Object} [extPrebid] properties will be merged into request.ext.prebid */ /** * @type {S2SDefaultConfig} */ const s2sDefaultConfig = { - enabled: false, timeout: 1000, maxBids: 1, adapter: 'prebidServer', @@ -64,30 +84,19 @@ config.setDefaults({ }); /** - * Set config for server to server header bidding - * @typedef {Object} options - required - * @property {boolean} enabled enables S2S bidding - * @property {string[]} bidders bidders to request S2S - * @property {string} endpoint endpoint to contact - * === optional params below === - * @property {number} [timeout] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` - * @property {number} [defaultTtl] ttl for S2S bidders when pbs does not return a ttl on the response - defaults to 60` - * @property {boolean} [cacheMarkup] whether to cache the adm result - * @property {string} [adapter] adapter code to use for S2S - * @property {string} [syncEndpoint] endpoint URL for syncing cookies - * @property {Object} [extPrebid] properties will be merged into request.ext.prebid - * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server + * @param {S2SConfig} option + * @return {boolean} */ -function setS2sConfig(options) { - if (options.defaultVendor) { - let vendor = options.defaultVendor; - let optionKeys = Object.keys(options); +function updateConfigDefaultVendor(option) { + if (option.defaultVendor) { + let vendor = option.defaultVendor; + let optionKeys = Object.keys(option); if (S2S_VENDORS[vendor]) { // vendor keys will be set if either: the key was not specified by user // or if the user did not set their own distinct value (ie using the system default) to override the vendor Object.keys(S2S_VENDORS[vendor]).forEach((vendorKey) => { - if (s2sDefaultConfig[vendorKey] === options[vendorKey] || !includes(optionKeys, vendorKey)) { - options[vendorKey] = S2S_VENDORS[vendor][vendorKey]; + if (s2sDefaultConfig[vendorKey] === option[vendorKey] || !includes(optionKeys, vendorKey)) { + option[vendorKey] = S2S_VENDORS[vendor][vendorKey]; } }); } else { @@ -95,9 +104,16 @@ function setS2sConfig(options) { return false; } } + // this is how we can know if user / defaultVendor has set it, or if we should default to false + return option.enabled = typeof option.enabled === 'boolean' ? option.enabled : false; +} - let keys = Object.keys(options); - +/** + * @param {S2SConfig} option + * @return {boolean} + */ +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'); @@ -105,10 +121,63 @@ function setS2sConfig(options) { } return false; }).length > 0) { + return false; + } +} + +// temporary change to modify the s2sConfig for new format used for endpoint URLs; +// 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])) { + let temp = option[prop]; + option[prop] = { p1Consent: temp, noP1Consent: temp }; + } + if (utils.isPlainObject(option[prop]) && (!option[prop].p1Consent || !option[prop].noP1Consent)) { + ['p1Consent', 'noP1Consent'].forEach((conUrl) => { + if (!option[prop][conUrl]) { + utils.logWarn(`s2sConfig.${prop}.${conUrl} not defined. PBS request will be skipped in some P1 scenarios.`); + } + }); + } + }); +} + +/** + * @param {(S2SConfig[]|S2SConfig)} options + */ +function setS2sConfig(options) { + if (!options) { return; } + const normalizedOptions = Array.isArray(options) ? options : [options]; + + const activeBidders = []; + const optionsValid = normalizedOptions.every((option, i, array) => { + formatUrlParams(options); + const updateSuccess = updateConfigDefaultVendor(option); + if (updateSuccess !== false) { + const valid = validateConfigRequiredProps(option); + if (valid !== false) { + if (Array.isArray(option['bidders'])) { + array[i]['bidders'] = option['bidders'].filter(bidder => { + if (activeBidders.indexOf(bidder) === -1) { + activeBidders.push(bidder); + return true; + } + return false; + }); + } + return true; + } + } + utils.logWarn('prebidServer: s2s config is disabled'); + return false; + }); - _s2sConfig = options; + if (optionsValid) { + return _s2sConfigs = normalizedOptions; + } } getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); @@ -116,51 +185,52 @@ getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); * resets the _synced variable back to false, primiarily used for testing purposes */ export function resetSyncedStatus() { - _synced = false; + _syncCount = 0; } /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent, uspConsent) { - if (_synced) { +function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { + if (_s2sConfigs.length === _syncCount) { return; } - _synced = true; + _syncCount++; const payload = { uuid: utils.generateUUID(), bidders: bidderCodes, - account: _s2sConfig.accountId + account: s2sConfig.accountId }; - let userSyncLimit = _s2sConfig.userSyncLimit; + let userSyncLimit = s2sConfig.userSyncLimit; if (utils.isNumber(userSyncLimit) && userSyncLimit > 0) { payload['limit'] = userSyncLimit; } if (gdprConsent) { - // only populate gdpr field if we know CMP returned consent information (ie didn't timeout or have an error) - if (typeof gdprConsent.consentString !== 'undefined') { - payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; - } + payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; // attempt to populate gdpr_consent if we know gdprApplies or it may apply if (gdprConsent.gdprApplies !== false) { payload.gdpr_consent = gdprConsent.consentString; } } - // US Privace (CCPA) support + // US Privacy (CCPA) support if (uspConsent) { payload.us_privacy = uspConsent; } + if (typeof s2sConfig.coopSync === 'boolean') { + payload.coopSync = s2sConfig.coopSync; + } + const jsonPayload = JSON.stringify(payload); - ajax(_s2sConfig.syncEndpoint, + ajax(getMatchingConsentUrl(s2sConfig.syncEndpoint, gdprConsent), (response) => { try { response = JSON.parse(response); - doAllSyncs(response.bidder_status); + doAllSyncs(response.bidder_status, s2sConfig); } catch (e) { utils.logError(e); } @@ -172,16 +242,20 @@ function queueSync(bidderCodes, gdprConsent, uspConsent) { }); } -function doAllSyncs(bidders) { +function doAllSyncs(bidders, s2sConfig) { if (bidders.length === 0) { return; } - const thisSync = bidders.pop(); + // pull the syncs off the list in the order that prebid server sends them + const thisSync = bidders.shift(); + + // 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)); + doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, utils.bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); } else { - doAllSyncs(bidders); + // bidder already has an ID, so just recurse to the next sync entry + doAllSyncs(bidders, s2sConfig); } } @@ -192,10 +266,11 @@ function doAllSyncs(bidders) { * @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 {S2SConfig} s2sConfig */ -function doPreBidderSync(type, url, bidder, done) { - if (_s2sConfig.syncUrlModifier && typeof _s2sConfig.syncUrlModifier[bidder] === 'function') { - const newSyncUrl = _s2sConfig.syncUrlModifier[bidder](type, url, bidder); +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) @@ -231,43 +306,31 @@ function doBidderSync(type, url, bidder, done) { * * @param {Array} bidders a list of bidder names */ -function doClientSideSyncs(bidders) { +function doClientSideSyncs(bidders, gdprConsent, uspConsent) { bidders.forEach(bidder => { let clientAdapter = adapterManager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { - clientAdapter.registerSyncs([]); + config.runWithBidder( + bidder, + utils.bind.call( + clientAdapter.registerSyncs, + clientAdapter, + [], + gdprConsent, + uspConsent + ) + ); } }); } -function _getDigiTrustQueryParams(bidRequest = {}) { - function getDigiTrustId(bidRequest) { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'bids.0.userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - const digiTrustUser = config.getConfig('digiTrustId'); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - let digiTrustId = getDigiTrustId(bidRequest); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } - return { - id: digiTrustId.id, - keyv: digiTrustId.keyv - }; -} - -function _appendSiteAppDevice(request, pageUrl) { +function _appendSiteAppDevice(request, pageUrl, accountId) { if (!request) return; // ORTB specifies app OR site if (typeof config.getConfig('app') === 'object') { request.app = config.getConfig('app'); - request.app.publisher = {id: _s2sConfig.accountId} + request.app.publisher = {id: accountId} } else { request.site = {}; if (utils.isPlainObject(config.getConfig('site'))) { @@ -275,7 +338,7 @@ function _appendSiteAppDevice(request, pageUrl) { } // set publisher.id if not already defined if (!utils.deepAccess(request.site, 'publisher.id')) { - utils.deepSetValue(request.site, 'publisher.id', _s2sConfig.accountId); + utils.deepSetValue(request.site, 'publisher.id', accountId); } // set site.page if not already defined if (!request.site.page) { @@ -300,18 +363,12 @@ function addBidderFirstPartyDataToRequest(request) { const bidderConfig = config.getBidderConfig(); const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.fpd) { - const fpd = {}; - if (currBidderConfig.fpd.context) { - fpd.site = currBidderConfig.fpd.context; - } - if (currBidderConfig.fpd.user) { - fpd.user = currBidderConfig.fpd.user; - } + if (currBidderConfig.ortb2) { + const ortb2 = utils.mergeDeep({}, currBidderConfig.ortb2); acc.push({ bidders: [ bidder ], - config: { fpd } + config: { ortb2 } }); } return acc; @@ -390,6 +447,18 @@ function addWurl(auctionId, adId, wurl) { } } +function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { + const bidderValues = utils.deepAccess(response, `ext.${pbsName}`); + if (bidderValues) { + Object.keys(bidderValues).forEach(bidder => { + let biddersReq = find(bidderRequests, bidderReq => bidderReq.bidderCode === bidder); + if (biddersReq) { + biddersReq[pbjsName] = bidderValues[bidder]; + } + }); + } +} + /** * @param {string} auctionId * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() @@ -418,18 +487,30 @@ export function resetWurlMap() { } const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits) { + buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; // transform ad unit into array of OpenRTB impression objects + let impIds = new Set(); adUnits.forEach(adUnit => { + // 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); + const nativeParams = processNativeAdUnitParams(utils.deepAccess(adUnit, 'mediaTypes.native')); 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) { @@ -495,13 +576,17 @@ const OPEN_RTB_PROTOCOL = { const bannerParams = utils.deepAccess(adUnit, 'mediaTypes.banner'); adUnit.bids.forEach(bid => { - // OpenRTB response contains the adunit code and bidder name. These are + // OpenRTB response contains imp.id 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; - + bidIdMap[`${impressionId}${bid.bidder}`] = bid.bid_id; // check for and store valid aliases to add to the request if (adapterManager.aliasRegistry[bid.bidder]) { - aliases[bid.bidder] = adapterManager.aliasRegistry[bid.bidder]; + const bidder = adapterManager.bidderRegistry[bid.bidder]; + // adding alias only if alias source bidder exists and alias isn't configured to be standalone + // pbs adapter + if (bidder && !bidder.getSpec().skipPbsAliasing) { + aliases[bid.bidder] = adapterManager.aliasRegistry[bid.bidder]; + } } }); @@ -518,14 +603,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 (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.'); } else { - mediaTypes['video'] = videoParams; + if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) { + videoParams.placement = 1; + } + + mediaTypes['video'] = Object.keys(videoParams).filter(param => param !== 'context') + .reduce((result, param) => { + if (param === 'playerSize') { + result.w = utils.deepAccess(videoParams, `${param}.0.0`); + result.h = utils.deepAccess(videoParams, `${param}.0.1`); + } else { + result[param] = videoParams[param]; + } + return result; + }, {}); } } @@ -551,25 +651,46 @@ const OPEN_RTB_PROTOCOL = { } // get bidder params in form { : {...params} } + // initialize reduce function with the user defined `ext` properties on the ad unit 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; + acc[bid.bidder] = (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params; return acc; - }, {}); - - const imp = { id: adUnit.code, ext, secure: _s2sConfig.secure }; - - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.adslot', pbAdSlot); - } + }, {...utils.deepAccess(adUnit, 'ortb2Imp.ext')}); + + const imp = { id: impressionId, ext, secure: s2sConfig.secure }; + + const ortb2 = {...utils.deepAccess(adUnit, '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]); + } else { + // remove pbadslot property if it doesn't meet the spec + delete imp.ext.data.pbadslot; + } + } 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}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); + } + }); Object.assign(imp, mediaTypes); @@ -579,6 +700,23 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); } + const getFloorBid = find(firstBidRequest.bids, bid => bid.adUnitCode === adUnit.code && typeof bid.getFloor === 'function'); + + if (getFloorBid) { + let floorInfo; + try { + floorInfo = getFloorBid.getFloor({ + currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, + }); + } catch (e) { + utils.logError('PBS: getFloor threw an error: ', e); + } + if (floorInfo && floorInfo.currency && !isNaN(parseFloat(floorInfo.floor))) { + imp.bidfloor = parseFloat(floorInfo.floor); + imp.bidfloorcur = floorInfo.currency + } + } + if (imp.banner || imp.video || imp.native) { imps.push(imp); } @@ -591,9 +729,10 @@ const OPEN_RTB_PROTOCOL = { const request = { id: s2sBidRequest.tid, source: {tid: s2sBidRequest.tid}, - tmax: _s2sConfig.timeout, + 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. @@ -608,9 +747,17 @@ const OPEN_RTB_PROTOCOL = { } }; + // Sets pbjs version, can be overwritten below if channel exists in s2sConfig.extPrebid + 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); + if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { + request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); } /** @@ -625,12 +772,7 @@ const OPEN_RTB_PROTOCOL = { request.cur = [adServerCur[0]]; } - _appendSiteAppDevice(request, firstBidRequest.refererInfo.referer); - - const digiTrust = _getDigiTrustQueryParams(firstBidRequest); - if (digiTrust) { - utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); - } + _appendSiteAppDevice(request, bidRequests[0].refererInfo.referer, s2sConfig.accountId); // pass schain object if it is present const schain = utils.deepAccess(bidRequests, '0.bids.0.schain'); @@ -641,7 +783,7 @@ const OPEN_RTB_PROTOCOL = { } if (!utils.isEmpty(aliases)) { - request.ext.prebid.aliases = aliases; + request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); @@ -649,6 +791,32 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } + if (utils.isArray(eidPermissions) && eidPermissions.length > 0) { + if (requestedBidders && utils.isArray(requestedBidders)) { + eidPermissions.forEach(i => { + if (i.bidders) { + i.bidders = i.bidders.filter(bidder => requestedBidders.includes(bidder)) + } + }); + } + utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); + } + + const multibid = config.getConfig('multibid'); + if (multibid) { + utils.deepSetValue(request, 'ext.prebid.multibid', multibid.reduce((result, i) => { + let obj = {}; + + Object.keys(i).forEach(key => { + obj[key.toLowerCase()] = i[key]; + }); + + result.push(obj); + + return result; + }, [])); + } + if (bidRequests) { if (firstBidRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module @@ -658,6 +826,9 @@ const OPEN_RTB_PROTOCOL = { } utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); utils.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); + } } // US Privacy (CCPA) support @@ -670,21 +841,20 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('fpd') || {}; - if (commonFpd.context) { - utils.deepSetValue(request, 'site.ext.data', commonFpd.context); - } - if (commonFpd.user) { - utils.deepSetValue(request, 'user.ext.data', commonFpd.user); - } + const commonFpd = getConfig('ortb2') || {}; + utils.mergeDeep(request, commonFpd); + addBidderFirstPartyDataToRequest(request); return request; }, - interpretResponse(response, bidderRequests) { + interpretResponse(response, bidderRequests, s2sConfig) { const bids = []; + [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] + .forEach(info => getPbsResponseData(bidderRequests, response, info[0], info[1])) + if (response.seatbid) { // a seatbid object contains a `bid` array and a `seat` string response.seatbid.forEach(seatbid => { @@ -699,7 +869,7 @@ const OPEN_RTB_PROTOCOL = { } const cpm = bid.price; - const status = cpm !== 0 ? STATUS.GOOD : STATUS.NO_BID; + const status = cpm !== 0 ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID; let bidObject = createBid(status, bidRequest || { bidder: seatbid.seat, src: TYPE @@ -707,6 +877,8 @@ const OPEN_RTB_PROTOCOL = { 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('.')); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; @@ -742,8 +914,8 @@ const OPEN_RTB_PROTOCOL = { if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; let sizes = bidRequest.sizes && bidRequest.sizes[0]; - bidObject.playerHeight = sizes[0]; - bidObject.playerWidth = sizes[1]; + bidObject.playerWidth = sizes[0]; + bidObject.playerHeight = sizes[1]; // try to get cache values from 'response.ext.prebid.cache.js' // else try 'bid.ext.prebid.targeting' as fallback @@ -785,7 +957,7 @@ const OPEN_RTB_PROTOCOL = { } if (utils.isPlainObject(adm) && Array.isArray(adm.assets)) { - let origAssets = nativeAssetCache[bidRequest.adUnitCode]; + let origAssets = nativeAssetCache[bid.impid]; bidObject.native = utils.cleanObj(adm.assets.reduce((native, asset) => { let origAsset = origAssets[asset.id]; if (utils.isPlainObject(asset.img)) { @@ -831,15 +1003,17 @@ const OPEN_RTB_PROTOCOL = { bidObject.creativeId = bid.crid; if (bid.burl) { bidObject.burl = bid.burl; } bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; - bidObject.meta = bidObject.meta || {}; + bidObject.meta = {}; + let extPrebidMeta = utils.deepAccess(bid, 'ext.prebid.meta'); + if (extPrebidMeta && utils.isPlainObject(extPrebidMeta)) { bidObject.meta = utils.deepClone(extPrebidMeta); } if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - // TODO: Remove when prebid-server returns ttl and netRevenue - const configTtl = _s2sConfig.defaultTtl || DEFAULT_S2S_TTL; - bidObject.ttl = (bid.ttl) ? bid.ttl : configTtl; + // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). + const configTtl = s2sConfig.defaultTtl || DEFAULT_S2S_TTL; + 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: bidRequest.adUnitCode, bid: bidObject }); }); }); } @@ -863,6 +1037,29 @@ function bidWonHandler(bid) { } } +function hasPurpose1Consent(gdprConsent) { + let result = true; + if (gdprConsent) { + if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { + result = !!(utils.deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +function getMatchingConsentUrl(urlProp, gdprConsent) { + return hasPurpose1Consent(gdprConsent) ? urlProp.p1Consent : urlProp.noP1Consent; +} + +function getConsentData(bidRequests) { + let gdprConsent, uspConsent; + if (Array.isArray(bidRequests) && bidRequests.length > 0) { + gdprConsent = bidRequests[0].gdprConsent; + uspConsent = bidRequests[0].uspConsent; + } + return { gdprConsent, uspConsent }; +} + /** * Bidder adapter for Prebid Server */ @@ -872,6 +1069,7 @@ 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 => @@ -884,39 +1082,40 @@ export function PrebidServer() { .reduce(utils.flatten) .filter(utils.uniques); - if (_s2sConfig && _s2sConfig.syncEndpoint) { - let gdprConsent, uspConsent; - if (Array.isArray(bidRequests) && bidRequests.length > 0) { - gdprConsent = bidRequests[0].gdprConsent; - uspConsent = bidRequests[0].uspConsent; - } - - let syncBidders = _s2sConfig.bidders - .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) - .filter((bidder, index, array) => (array.indexOf(bidder) === index)); + if (Array.isArray(_s2sConfigs)) { + if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { + let syncBidders = s2sBidRequest.s2sConfig.bidders + .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) + .filter((bidder, index, array) => (array.indexOf(bidder) === index)); - queueSync(syncBidders, gdprConsent, uspConsent); - } + queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); + } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits); - const requestJson = request && JSON.stringify(request); - if (request && requestJson) { - ajax( - _s2sConfig.endpoint, - { - success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done), - error: done - }, - requestJson, - { contentType: 'text/plain', withCredentials: true } - ); + const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); + const requestJson = request && JSON.stringify(request); + utils.logInfo('BidRequest: ' + requestJson); + const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + if (request && requestJson && endpointUrl) { + ajax( + endpointUrl, + { + success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done, s2sBidRequest.s2sConfig), + error: done + }, + requestJson, + { contentType: 'text/plain', withCredentials: true } + ); + } else { + utils.logError('PBS request not made. Check endpoints.'); + } } }; /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done) { + function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done, s2sConfig) { let result; let bids = []; + let { gdprConsent, uspConsent } = getConsentData(bidderRequests); try { result = JSON.parse(response); @@ -924,7 +1123,7 @@ export function PrebidServer() { bids = OPEN_RTB_PROTOCOL.interpretResponse( result, bidderRequests, - requestedBidders + s2sConfig ); bids.forEach(({adUnit, bid}) => { @@ -933,7 +1132,7 @@ export function PrebidServer() { } }); - bidderRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); + bidderRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } catch (error) { utils.logError(error); } @@ -943,11 +1142,11 @@ export function PrebidServer() { } done(); - doClientSideSyncs(requestedBidders); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); } // Listen for bid won to call wurl - events.on(EVENTS.BID_WON, bidWonHandler); + events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); return Object.assign(this, { callBids: baseAdapter.callBids, @@ -956,4 +1155,14 @@ export function PrebidServer() { }); } +/** + * Global setter that sets eids permissions for bidders + * This setter is to be used by userId module when included + * @param {array} newEidPermissions + */ +function setEidPermissions(newEidPermissions) { + eidPermissions = newEidPermissions; +} +getPrebidInternal().setEidPermissions = setEidPermissions; + adapterManager.registerBidAdapter(new PrebidServer(), 'prebidServer'); diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index b98ca864cd5..b9a7d79f991 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -1,10 +1,12 @@ import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ +export const storage = getStorageManager(undefined, 'prebidmanager'); const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; @@ -82,14 +84,14 @@ function collectUtmTagData() { }); if (newUtm === false) { utmTags.forEach(function (utmKey) { - let itemValue = localStorage.getItem(`pm_${utmKey}`); - if (itemValue.length !== 0) { + let itemValue = storage.getDataFromLocalStorage(`pm_${utmKey}`); + if (itemValue && itemValue.length !== 0) { pmUtmTags[utmKey] = itemValue; } }); } else { utmTags.forEach(function (utmKey) { - localStorage.setItem(`pm_${utmKey}`, pmUtmTags[utmKey]); + storage.setDataInLocalStorage(`pm_${utmKey}`, pmUtmTags[utmKey]); }); } } catch (e) { @@ -99,6 +101,16 @@ function collectUtmTagData() { return pmUtmTags; } +function collectPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + if (document.referrer) { + pageInfo.referrerDomain = utils.parseUrl(document.referrer).hostname; + } + return pageInfo; +} + function flush() { if (!pmAnalyticsEnabled) { return; @@ -111,6 +123,7 @@ function flush() { bundleId: initOptions.bundleId, events: _eventQueue, utmTags: collectUtmTagData(), + pageInfo: collectPageInfo(), }; ajax( diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 6d35a0a74cc..5f7a39e0784 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -55,7 +55,7 @@ export let _floorDataForAuction = {}; * @summary Simple function to round up to a certain decimal degree */ function roundUp(number, precision) { - return Math.ceil(parseFloat(number) * Math.pow(10, precision)) / Math.pow(10, precision); + return Math.ceil((parseFloat(number) * Math.pow(10, precision)).toFixed(1)) / Math.pow(10, precision); } let referrerHostname; @@ -64,13 +64,19 @@ function getHostNameFromReferer(referer) { return referrerHostname; } +// First look into bidRequest! +function getGptSlotFromBidRequest(bidRequest) { + const isGam = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.name') === 'gam'; + return isGam && bidRequest.ortb2Imp.ext.data.adserver.adslot; +} + /** * @summary floor field types with their matching functions to resolve the actual matched value */ export let fieldMatchingFunctions = { 'size': (bidRequest, bidResponse) => utils.parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', - 'gptSlot': (bidRequest, bidResponse) => utils.getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode).gptSlot, + 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromBidRequest(bidRequest) || utils.getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode).gptSlot, 'domain': (bidRequest, bidResponse) => referrerHostname || getHostNameFromReferer(getRefererInfo().referer), 'adUnitCode': (bidRequest, bidResponse) => bidRequest.adUnitCode } @@ -98,21 +104,23 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let fieldValues = enumeratePossibleFieldValues(utils.deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); if (!fieldValues.length) return { matchingFloor: floorData.default }; - // look to see iof a request for this context was made already + // 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}`); if (previousMatch) { - return previousMatch; + return {...previousMatch}; } let allPossibleMatches = generatePossibleEnumerations(fieldValues, utils.deepAccess(floorData, 'schema.delimiter') || '|'); let matchingRule = find(allPossibleMatches, hashValue => floorData.values.hasOwnProperty(hashValue)); let matchingData = { - matchingFloor: floorData.values[matchingRule] || floorData.default, + floorMin: floorData.floorMin || 0, + floorRuleValue: floorData.values[matchingRule] || floorData.default, matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters matchingRule }; + matchingData.matchingFloor = Math.max(matchingData.floorMin, matchingData.floorRuleValue); // save for later lookup if needed utils.deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); return matchingData; @@ -138,10 +146,10 @@ 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) { - const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`); +export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { + const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`) || utils.deepAccess(getGlobal(), 'bidderSettings.standard.bidCpmAdjustment'); if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm)); + return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); } return parseFloat(inputCpm); } @@ -286,11 +294,15 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { bid.auctionId = auctionId; bid.floorData = { skipped: floorData.skipped, - modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), - location: utils.deepAccess(floorData, 'data.location'), 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'), + floorProvider: floorData.floorProvider, fetchStatus: _floorsConfig.fetchStatus - } + }; }); }); } @@ -335,6 +347,8 @@ export function createFloorsDataForAuction(adUnits, auctionId) { const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; } + // copy FloorMin to floorData.data + if (resolvedFloorsData.hasOwnProperty('floorMin')) resolvedFloorsData.data.floorMin = resolvedFloorsData.floorMin; // add floorData to bids updateAdUnitsForAuction(adUnits, resolvedFloorsData, auctionId); return resolvedFloorsData; @@ -513,6 +527,7 @@ export function handleFetchResponse(fetchResponse) { _floorsConfig.data = fetchData; // set skipRate override if necessary _floorsConfig.skipRate = utils.isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate; + _floorsConfig.floorProvider = fetchData.floorProvider || _floorsConfig.floorProvider; } // if any auctions are waiting for fetch to finish, we need to continue them! @@ -566,8 +581,10 @@ function addFieldOverrides(overrides) { */ export function handleSetFloorsConfig(config) { _floorsConfig = utils.pick(config, [ + 'floorMin', 'enabled', enabled => enabled !== false, // defaults to true 'auctionDelay', auctionDelay => auctionDelay || 0, + 'floorProvider', floorProvider => utils.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 || {}, [ @@ -620,6 +637,7 @@ function addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm) { bid.floorData = { floorValue: floorInfo.matchingFloor, floorRule: floorInfo.matchingRule, + floorRuleValue: floorInfo.floorRuleValue, floorCurrency: floorData.data.currency, cpmAfterAdjustments: adjustedCpm, enforcements: {...floorData.enforcement}, @@ -679,7 +697,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm); + adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/modules/priceFloors.md b/modules/priceFloors.md index d09be78c620..36ac07ee972 100644 --- a/modules/priceFloors.md +++ b/modules/priceFloors.md @@ -11,6 +11,7 @@ pbjs.setConfig({ enforceJS: true //defaults to true }, auctionDelay: 150, // in milliseconds defaults to 0 + floorProvider: 'awesomeFloorProviderName', // name of the floor provider (optional) endpoint: { url: 'http://localhost:1500/floor-domains', method: 'GET' // Only get supported for now @@ -40,6 +41,7 @@ pbjs.setConfig({ | enabled | Wether to turn off or on the floors module | | enforcement | object of booleans which control certain features of the module | | auctionDelay | The time to suspend and auction while waiting for a real time price floors fetch to come back | +| floorProvider | A string identifying the floor provider.| | endpoint | An object describing the endpoint to retrieve floor data from. GET only | | data | The data to be used to select appropriate floors. See schema for more detail | | additionalSchemaFields | An object of additional fields to be used in a floor data object. The schema is KEY: function to retrieve the match | @@ -59,4 +61,4 @@ This function can takes in an object with the following optional parameters: If a bid adapter passes in `*` as an attribute, then the `priceFloors` module will attempt to select the best rule based on context. -For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it. \ No newline at end of file +For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it. diff --git a/modules/projectLimeLightBidAdapter.js b/modules/projectLimeLightBidAdapter.js deleted file mode 100644 index f2ff77f6229..00000000000 --- a/modules/projectLimeLightBidAdapter.js +++ /dev/null @@ -1,125 +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'; -const URL = 'https://ads.project-limelight.com/hb'; - -/** - * 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; -} - -function extractBidSizes(bid) { - const bidSizes = []; - - bid.sizes.forEach(size => { - bidSizes.push({ - width: size[0], - height: size[1] - }); - }); - - return bidSizes; -} - -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 = []; - const request = { - 'secure': (location.protocol === 'https:'), - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'adUnits': placements - }; - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - const params = bid.params; - placements.push({ - id: params.adUnitId, - bidId: bid.bidId, - transactionId: bid.transactionId, - sizes: extractBidSizes(bid), - type: params.adUnitType.toUpperCase() - }); - } - return { - method: 'POST', - url: URL, - data: request - }; - }, - - 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); diff --git a/modules/projectLimeLightBidAdapter.md b/modules/projectLimeLightBidAdapter.md deleted file mode 100644 index 71621983b89..00000000000 --- a/modules/projectLimeLightBidAdapter.md +++ /dev/null @@ -1,55 +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: { - adUnitId: 0, - adUnitType: 'banner' - } - }] - } - ]; -``` - -# Test Parameters for video -``` -var videoAdUnit = [{ - code: 'video1', - sizes: [[300, 250]], - bids: [{ - bidder: 'project-limelight', - params: { - 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 c546337c47f..8b191c70e75 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,59 +1,119 @@ - import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import * as utils from '../src/utils.js'; + const BIDDER_CODE = 'proxistore'; -const storage = getStorageManager(); 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) { - const sizeIds = []; - + var sizeIds = []; bidRequests.forEach(function (bid) { - const sizeId = { + var sizeId = { id: bid.bidId, sizes: bid.sizes.map(function (size) { return { width: size[0], - height: size[1] + height: size[1], }; - }) + }), + floor: _assignFloor(bid), + segments: _assignSegments(bid), }; sizeIds.push(sizeId); }); - const payload = { + var payload = { auctionId: bidRequests[0].auctionId, transactionId: bidRequests[0].auctionId, bids: sizeIds, website: bidRequests[0].params.website, language: bidRequests[0].params.language, gdpr: { - applies: false - } - }; - const options = { - contentType: 'application/json', - withCredentials: true + applies: false, + consentGiven: false, + }, }; if (bidderRequest && bidderRequest.gdprConsent) { - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) { + var gdprConsent = bidderRequest.gdprConsent; + + if ( + typeof gdprConsent.gdprApplies === 'boolean' && + gdprConsent.gdprApplies + ) { payload.gdpr.applies = true; } - if (typeof bidderRequest.gdprConsent.consentString === 'string' && bidderRequest.gdprConsent.consentString) { + if ( + typeof gdprConsent.consentString === 'string' && + gdprConsent.consentString + ) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } - if (bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents && typeof bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined') { - payload.gdpr.consentGiven = !!bidderRequest.gdprConsent.vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; + if (gdprConsent.vendorData) { + 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)]; + } } } + var options = { + contentType: 'application/json', + withCredentials: payload.gdpr.consentGiven, + customHeaders: { + version: '1.0.4', + }, + }; + var endPointUri = + payload.gdpr.consentGiven || !payload.gdpr.applies + ? COOKIE_BASE_URL + : COOKIE_LESS_URL; + return { method: 'POST', - url: bidRequests[0].params.url || 'https://abs.proxistore.com/' + payload.language + '/v3/rtb/prebid/multi', + url: endPointUri, data: JSON.stringify(payload), - options: options + options: options, + }; +} + +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: {}, + } + ); + } + + return { + segments: [], + contextual_categories: {}, }; } @@ -70,7 +130,8 @@ function _createBidResponse(response) { netRevenue: response.netRevenue, vastUrl: response.vastUrl, vastXml: response.vastXml, - dealId: response.dealId + dealId: response.dealId, + meta: response.meta, }; } /** @@ -81,23 +142,8 @@ function _createBidResponse(response) { */ function isBidRequestValid(bid) { - const hasNoAd = function() { - if (!storage.hasLocalStorage()) { - return false; - } - const pxNoAds = storage.getDataFromLocalStorage(`PX_NoAds_${bid.params.website}`); - if (!pxNoAds) { - return false; - } else { - const storedDate = new Date(pxNoAds); - const now = new Date(); - const diff = Math.abs(storedDate.getTime() - now.getTime()) / 60000; - return diff <= 5; - } - } - return !!(bid.params.website && bid.params.language) && !hasNoAd(); + return !!(bid.params.website && bid.params.language); } - /** * Make a server request from the list of BidRequests. * @@ -107,7 +153,8 @@ function isBidRequestValid(bid) { */ function buildRequests(bidRequests, bidderRequest) { - const request = _createServerRequest(bidRequests, bidderRequest); + var request = _createServerRequest(bidRequests, bidderRequest); + return request; } /** @@ -119,34 +166,29 @@ function buildRequests(bidRequests, bidderRequest) { */ function interpretResponse(serverResponse, bidRequest) { - const itemName = `PX_NoAds_${websiteFromBidRequest(bidRequest)}`; - if (serverResponse.body.length > 0) { - storage.removeDataFromLocalStorage(itemName, true); - return serverResponse.body.map(_createBidResponse); - } else { - storage.setDataInLocalStorage(itemName, new Date()); - return []; - } + return serverResponse.body.map(_createBidResponse); } -const websiteFromBidRequest = function(bidR) { - if (bidR.data) { - return JSON.parse(bidR.data).website - } else if (bidR.params.website) { - return bidR.params.website; +function _assignFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + // eslint-disable-next-line no-console + console.log(bid.params.bidFloor); + return bid.params.bidFloor ? bid.params.bidFloor : null; } -} - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param syncOptions Which user syncs are allowed? - * @param serverResponses List of server's responses. - * @return The user syncs which should be dropped. - */ + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: 'banner', + size: '*', + }); -function getUserSyncs(syncOptions, serverResponses) { - return []; + if ( + utils.isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === 'EUR' + ) { + return floor.floor; + } + return null; } export const spec = { @@ -154,7 +196,6 @@ export const spec = { isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, - getUserSyncs: getUserSyncs }; registerBidder(spec); diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 174fa6ffe6e..427f775c44b 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -212,9 +212,11 @@ export function requestBidHook(next, config) { // into bid requests later. if (adUnits && pubcid) { adUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - Object.assign(bid, {crumbs: {pubcid}}); - }); + if (unit.bids && utils.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 8e2be1207f5..00000000000 --- a/modules/pubCommonIdSystem.js +++ /dev/null @@ -1,96 +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'; - -const PUB_COMMON_ID = 'PublisherCommonId'; - -const MODULE_NAME = 'pubCommonId'; - -/** @type {Submodule} */ -export const pubCommonIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * 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 - * @returns {{pubcid:string}} - */ - decode(value) { - return { 'pubcid': value } - }, - /** - * performs action to obtain id - * @function - * @param {SubmoduleParams} [configParams] - * @returns {IdResponse} - */ - getId: function ({create = true, pixelUrl} = {}) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - // If the page includes its own pubcid module, then save a copy of id. - return {id: window[PUB_COMMON_ID].getId()}; - } - } catch (e) { - } - - const newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined; - return { - id: newId, - callback: this.makeCallback(pixelUrl, newId) - } - }, - /** - * performs action to extend an id - * @function - * @param {SubmoduleParams} [configParams] - * @param {Object} storedId existing id - * @returns {IdResponse|undefined} - */ - extendId: function({extend = false, pixelUrl} = {}, storedId) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - // If the page includes its onw pubcid module, then there is nothing to do. - return; - } - } catch (e) { - } - - if (extend) { - // When extending, only one of response fields is needed - const callback = this.makeCallback(pixelUrl, storedId); - return callback ? {callback: callback} : {id: storedId}; - } - } -}; - -submodule('userId', pubCommonIdSubmodule); diff --git a/modules/pubProvidedIdSystem.js b/modules/pubProvidedIdSystem.js new file mode 100644 index 00000000000..0b2175f57cb --- /dev/null +++ b/modules/pubProvidedIdSystem.js @@ -0,0 +1,54 @@ +/** + * This module adds Publisher Provided ids support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/pubProvidedSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'pubProvidedId'; + +/** @type {Submodule} */ +export const pubProvidedIdSubmodule = { + + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid request + * @function + * @param {string} value + * @returns {{pubProvidedId: array}} or undefined if value doesn't exists + */ + decode(value) { + const res = value ? {pubProvidedId: value} : undefined; + utils.logInfo('PubProvidedId: Decoded value ' + JSON.stringify(res)); + return res; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @returns {{id: array}} + */ + getId(config) { + const configParams = (config && config.params) || {}; + let res = []; + if (utils.isArray(configParams.eids)) { + res = res.concat(configParams.eids); + } + if (typeof configParams.eidsFunction === 'function') { + res = res.concat(configParams.eidsFunction()); + } + return {id: res}; + } +}; + +// Register submodule for userId +submodule('userId', pubProvidedIdSubmodule); diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index d42073b3da5..89dea545434 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -1,27 +1,27 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, deepSetValue, inIframe, isArrayOfNums, + isFn, isInteger, - isNumber, isStr, logError, parseQueryStringParameters, + pick, } from '../src/utils.js'; -const BIDDER_VERSION = '1.0.0'; +const BIDDER_VERSION = '1.1.0'; const BASE_URL = 'https://ortb.adpearl.io'; -const AUCTION_URL = BASE_URL + '/prebid/auction'; export const spec = { code: 'pubgenius', - supportedMediaTypes: [ BANNER ], + supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid(bid) { const adUnitId = bid.params.adUnitId; @@ -30,15 +30,20 @@ export const spec = { return false; } - const sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - return Boolean(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2)); + const { mediaTypes } = bid; + + if (mediaTypes.banner) { + return isValidBanner(mediaTypes.banner); + } + + return isValidVideo(mediaTypes.video, bid.params.video); }, buildRequests: function (bidRequests, bidderRequest) { const data = { id: bidderRequest.auctionId, imp: bidRequests.map(buildImp), - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, ext: { pbadapter: { version: BIDDER_VERSION, @@ -66,7 +71,7 @@ export const spec = { deepSetValue(data, 'regs.ext.us_privacy', usp); } - const schain = bidderRequest.schain; + const schain = bidRequests[0].schain; if (schain) { deepSetValue(data, 'source.ext.schain', schain); } @@ -85,7 +90,7 @@ export const spec = { return { method: 'POST', - url: AUCTION_URL, + url: `${getBaseUrl()}/prebid/auction`, data, }; }, @@ -128,7 +133,7 @@ export const spec = { const qs = parseQueryStringParameters(params); syncs.push({ type: 'iframe', - url: `${BASE_URL}/usersync/pixels.html?${qs}`, + url: `${getBaseUrl()}/usersync/pixels.html?${qs}`, }); } @@ -136,25 +141,75 @@ export const spec = { }, onTimeout(data) { - ajax(`${BASE_URL}/prebid/events?type=timeout`, null, JSON.stringify(data), { + ajax(`${getBaseUrl()}/prebid/events?type=timeout`, null, JSON.stringify(data), { method: 'POST', }); }, }; +function buildVideoParams(videoMediaType, videoParams) { + videoMediaType = videoMediaType || {}; + const params = pick(videoMediaType, [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + ]); + + switch (videoMediaType.context) { + case 'instream': + params.placement = 1; + break; + case 'outstream': + params.placement = 2; + break; + default: + break; + } + + if (videoMediaType.playerSize) { + params.w = videoMediaType.playerSize[0][0]; + params.h = videoMediaType.playerSize[0][1]; + } + + return Object.assign(params, videoParams); +} + function buildImp(bid) { const imp = { id: bid.bidId, - banner: { - format: deepAccess(bid, 'mediaTypes.banner.sizes').map(size => ({ w: size[0], h: size[1] })), - topframe: numericBoolean(!inIframe()), - }, tagid: String(bid.params.adUnitId), }; - const bidFloor = bid.params.bidFloor; - if (isNumber(bidFloor)) { - imp.bidfloor = bidFloor; + if (bid.mediaTypes.banner) { + imp.banner = { + format: bid.mediaTypes.banner.sizes.map(size => ({ w: size[0], h: size[1] })), + topframe: numericBoolean(!inIframe()), + }; + } else { + imp.video = buildVideoParams(bid.mediaTypes.video, bid.params.video); + } + + if (isFn(bid.getFloor)) { + const { floor } = bid.getFloor({ + mediaType: bid.mediaTypes.banner ? 'banner' : 'video', + size: '*', + currency: 'USD', + }); + + if (floor) { + imp.bidfloor = floor; + } } const pos = bid.params.position; @@ -170,14 +225,26 @@ function buildImp(bid) { } function buildSite(bidderRequest) { - const pageUrl = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + let site = null; + const { refererInfo } = bidderRequest; + + const pageUrl = config.getConfig('pageUrl') || refererInfo.canonicalUrl || refererInfo.referer; if (pageUrl) { - return { - page: pageUrl, - }; + site = site || {}; + site.page = pageUrl; } - return null; + if (refererInfo.reachedTop) { + try { + const pageRef = window.top.document.referrer; + if (pageRef) { + site = site || {}; + site.ref = pageRef; + } + } catch (e) {} + } + + return site; } function interpretBid(bid) { @@ -186,7 +253,6 @@ function interpretBid(bid) { cpm: bid.price, width: bid.w, height: bid.h, - ad: bid.adm, ttl: bid.exp, creativeId: bid.crid, netRevenue: true, @@ -198,6 +264,24 @@ function interpretBid(bid) { }; } + const pbadapter = deepAccess(bid, 'ext.pbadapter') || {}; + switch (pbadapter.mediaType) { + case 'video': + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } + + if (bid.adm) { + bidResponse.vastXml = bid.adm; + } + + bidResponse.mediaType = VIDEO; + break; + default: // banner by default + bidResponse.ad = bid.adm; + break; + } + return bidResponse; } @@ -205,4 +289,27 @@ function numericBoolean(value) { return value ? 1 : 0; } +function getBaseUrl() { + const pubg = config.getConfig('pubgenius'); + return (pubg && pubg.endpoint) || BASE_URL; +} + +function isValidSize(size) { + return isArrayOfNums(size, 2) && size[0] > 0 && size[1] > 0; +} + +function isValidBanner(banner) { + const sizes = banner.sizes; + return !!(sizes && sizes.length) && sizes.every(isValidSize); +} + +function isValidVideo(videoMediaType, videoParams) { + const params = buildVideoParams(videoMediaType, videoParams); + + return !!(params.placement && + isValidSize([params.w, params.h]) && + params.mimes && params.mimes.length && + isArrayOfNums(params.protocols) && params.protocols.length); +} + registerBidder(spec); diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index 66851af9c3f..2a10b421de2 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -12,8 +12,6 @@ Module that connects to pubGENIUS's demand sources # Test Parameters -Test bids have $0.01 CPM by default. Use `bidFloor` in bidder params to control CPM for testing purposes. - ``` var adUnits = [ { @@ -45,12 +43,43 @@ var adUnits = [ bidder: 'pubgenius', params: { adUnitId: '1000', - bidFloor: 0.5, test: true } } ] - } + }, + { + code: 'test-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [3], + } + }, + bids: [ + { + bidder: 'pubgenius', + params: { + adUnitId: '1001', + test: true, + + // Other video parameters can be put here as in OpenRTB v2.5 spec. + // This overrides the same parameters in mediaTypes.video. + video: { + placement: 1, + + // w and h overrides mediaTypes.video.playerSize. + w: 640, + h: 360, + + skip: 1 + } + } + } + ] + }, ]; ``` diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js new file mode 100644 index 00000000000..5e549732097 --- /dev/null +++ b/modules/publinkIdSystem.js @@ -0,0 +1,123 @@ +/** + * 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 * as utils 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 publinkIdUrl(params, consentData) { + let url = utils.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; + } + + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString && typeof usPrivacyString === 'string') { + url.search.us_privacy = usPrivacyString; + } + + return utils.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) { + ajax(publinkIdUrl(config.params, consentData), handleResponse, undefined, options); + } + }; +} + +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') { + try { + const obj = JSON.parse(value); + if (obj && obj.exp && obj.exp > Date.now()) { + return obj.publink; + } + } catch (e) { + utils.logError(e); + } + } + } + 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} id encrypted userid + * @returns {{publinkId: string} | undefined} + */ + decode(publinkId) { + return {publink: 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 + * @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..669828322a5 --- /dev/null +++ b/modules/publinkIdSystem.md @@ -0,0 +1,28 @@ +## 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 | `"e80b5017098950fc58aad83c8c14978e"` | + +### Example configuration for Publink +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "publinkId", + storage: { + name: "pbjs_publink", + type: "html5" + }, + params: { + e: "e80b5017098950fc58aad83c8c14978e", // example hashed email (md5) + } + }], + } + }); +``` diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index d94c098db5d..9aa40a2282d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -82,7 +82,7 @@ function setMediaTypes(types, bid) { function copyRequiredBidDetails(bid) { return utils.pick(bid, [ - 'bidder', bidder => bidder.toLowerCase(), + 'bidder', 'bidId', 'status', () => NO_BID, // default a bid to NO_BID until response is recieved or bid is timed out 'finalSource as source', @@ -152,6 +152,7 @@ function parseBidResponse(bid) { 'mediaType', 'params', 'mi', + 'regexPattern', () => bid.regexPattern || undefined, 'partnerImpId', // partner impression ID 'dimensions', () => utils.pick(bid, [ 'width', @@ -166,15 +167,49 @@ function getDomainFromUrl(url) { return a.hostname; } +function getDevicePlatform() { + var deviceType = 3; + try { + var ua = navigator.userAgent; + if (ua && utils.isStr(ua) && ua.trim() != '') { + ua = ua.toLowerCase().trim(); + var isMobileRegExp = new RegExp('(mobi|tablet|ios).*'); + if (ua.match(isMobileRegExp)) { + deviceType = 2; + } else { + deviceType = 1; + } + } + } catch (ex) {} + return deviceType; +} + +function getValueForKgpv(bid, adUnitId) { + if (bid.params.regexPattern) { + return bid.params.regexPattern; + } else if (bid.bidResponse && bid.bidResponse.regexPattern) { + return bid.bidResponse.regexPattern; + } else if (bid.params.kgpv) { + return bid.params.kgpv; + } else { + return adUnitId; + } +} + +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +} + 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': bid.params.kgpv ? bid.params.kgpv : adUnitId, + 'kgpv': getValueForKgpv(bid, adUnitId), 'kgpsv': bid.params.kgpv ? bid.params.kgpv : adUnitId, 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, @@ -220,6 +255,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tst'] = Math.round((new window.Date()).getTime() / 1000); outputObj['pid'] = '' + profileId; outputObj['pdvid'] = '' + profileVersionId; + outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = (function() { var testGroupId = parseInt(config.getConfig('testGroupId') || 0); if (testGroupId <= 15 && testGroupId >= 0) { @@ -228,13 +264,6 @@ function executeBidsLoggerCall(e, highestCpmBids) { return 0; })(); - // GDPR support - if (auctionCache.gdprConsent) { - outputObj['cns'] = auctionCache.gdprConsent.consentString || ''; - outputObj['gdpr'] = auctionCache.gdprConsent.gdprApplies === true ? 1 : 0; - pixelURL += '&gdEn=1'; - } - outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { let adUnit = auctionCache.adUnitCodes[adUnitId]; let slotObject = { @@ -263,6 +292,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 || ''); @@ -272,10 +302,11 @@ 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(winningBid.params.kgpv || adUnitId); + pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&piid=' + enc(winningBid.bidResponse.partnerImpId || EMPTY_STRING); ajax( pixelURL, @@ -307,7 +338,6 @@ function auctionInitHandler(args) { } function bidRequestedHandler(args) { - cache.auctions[args.auctionId].gdprConsent = args.gdprConsent || undefined; args.bids.forEach(function(bid) { if (!cache.auctions[args.auctionId].adUnitCodes.hasOwnProperty(bid.adUnitCode)) { cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode] = { diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a050d499640..7edeb5589d1 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -2,11 +2,12 @@ import * as utils 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'; +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; @@ -14,6 +15,8 @@ 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 CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -45,7 +48,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 = { @@ -98,13 +102,78 @@ const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ } ] -const NET_REVENUE = false; +const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', 5: 'PREF', 6: 'PMPG' }; +const FLOC_FORMAT = { + 'EID': 1, + 'SEGMENT': 2 +} +// BB stands for Blue BillyWig +const BB_RENDERER = { + bootstrapPlayer: function(bid) { + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) config.vastXml = bid.vastXml; + 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...`); + return; + } + + const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); + + const ele = document.getElementById(bid.adUnitCode); // NB convention + + let renderer; + + for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { + if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { + renderer = window.bluebillywig.renderers[rendererIndex]; + break; + } + } + + if (renderer) renderer.bootstrap(config, ele); + else utils.logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); + }, + newRenderer: function(rendererCode, adUnitCode) { + var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(BB_RENDERER.outstreamRender); + } catch (err) { + utils.logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); + }, + getRendererId: function(pub, renderer) { + return `${pub}-${renderer}`; // NB convention! + } +}; + +const MEDIATYPE = [ + BANNER, + VIDEO, + NATIVE +] + let publisherId = 0; let isInvalidNativeRequest = false; let NATIVE_ASSET_ID_TO_KEY_MAP = {}; @@ -147,6 +216,9 @@ function _cleanSlot(slotName) { if (utils.isStr(slotName)) { return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); } + if (slotName) { + utils.logWarn(BIDDER_CODE + ': adSlot must be a string. Ignoring adSlot'); + } return ''; } @@ -466,7 +538,7 @@ function _createBannerRequest(bid) { } function _createVideoRequest(bid) { - var videoData = bid.params.video; + var videoData = utils.mergeDeep(utils.deepAccess(bid.mediaTypes, 'video'), bid.params.video); var videoObj; if (videoData !== UNDEFINED) { @@ -484,11 +556,6 @@ function _createVideoRequest(bid) { 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.'); @@ -516,6 +583,48 @@ function _addPMPDealsInImpression(impObj, bid) { } } +function _addDealCustomTargetings(imp, bid) { + var dctr = ''; + var dctrLen; + if (bid.params.dctr) { + dctr = bid.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); + } + imp.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'); + } + } +} + +function _addJWPlayerSegmentData(imp, bid) { + 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'; + } + const ext = imp.ext; + (ext && ext.key_val === undefined) + ? ext.key_val = jwPlayerData + : ext.key_val += '|' + jwPlayerData; +} + function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; @@ -537,7 +646,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) { @@ -585,6 +695,8 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } + _addImpressionFPD(impObj, bid); + _addFloorFromFloorModule(impObj, bid); return impObj.hasOwnProperty(BANNER) || @@ -592,6 +704,36 @@ function _createImpressionObject(bid, conf) { impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } +function _addImpressionFPD(imp, bid) { + const ortb2 = {...utils.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]); + } 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}`); + if (typeof value === 'string' && value) { + utils.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); + } + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); + } + }); +} + function _addFloorFromFloorModule(impObj, bid) { let bidFloor = -1; // get lowest floor from floorModule @@ -616,29 +758,94 @@ function _addFloorFromFloorModule(impObj, bid) { impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); } +function _getFlocId(validBidRequests, flocFormat) { + var flocIdObject = null; + var flocId = utils.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'); + let bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + let flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.EID); + if (flocObject) { + if (!bidUserIdAsEids) { + bidUserIdAsEids = []; + } + bidUserIdAsEids.push(flocObject); + } if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { utils.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; + utils.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) { + utils.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); } } } @@ -728,38 +935,32 @@ function _blockedIabCategoriesValidation(payload, 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'); +function _assignRenderer(newBid, request) { + let bidParams, context, adUnitCode; + if (request.bidderRequest && request.bidderRequest.bids) { + for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { + if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { + bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; } - 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'); + } + if (context && context === 'outstream' && bidParams && bidParams.outstreamAU && adUnitCode) { + newBid.rendererCode = bidParams.outstreamAU; + newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode); } } } +function isNonEmptyArray(test) { + if (utils.isArray(test) === true) { + if (test.length > 0) { + return true; + } + } + return false; +} + export const spec = { code: BIDDER_CODE, gvlid: 76, @@ -773,15 +974,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. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + 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)); 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 = utils.deepAccess(bid.mediaTypes, 'video.mimes'); + let paramsVideoMimes = utils.deepAccess(bid, 'params.video.mimes'); + if (isNonEmptyArray(mediaTypesVideoMimes) === false && isNonEmptyArray(paramsVideoMimes) === false) { + utils.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.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')) { + // 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]; + utils.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 { + utils.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; + } + } } return true; } @@ -851,7 +1079,7 @@ export const spec = { payload.ext.wrapper = {}; payload.ext.wrapper.profile = parseInt(conf.profId) || UNDEFINED; payload.ext.wrapper.version = parseInt(conf.verId) || UNDEFINED; - payload.ext.wrapper.wiid = conf.wiid || UNDEFINED; + payload.ext.wrapper.wiid = conf.wiid || bidderRequest.auctionId; // eslint-disable-next-line no-undef payload.ext.wrapper.wv = $$REPO_AND_VERSION$$; payload.ext.wrapper.transactionId = conf.transactionId; @@ -865,6 +1093,11 @@ export const spec = { payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); payload.site.domain = _getDomainFromURL(payload.site.page); + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + // merge the device from config.getConfig('device') if (typeof config.getConfig('device') === 'object') { payload.device = Object.assign(payload.device, config.getConfig('device')); @@ -899,9 +1132,17 @@ export const spec = { utils.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}); + } + if (commonFpd.user) { + utils.mergeDeep(payload, {user: commonFpd.user}); + } // Note: Do not move this block up // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object @@ -910,13 +1151,19 @@ export const spec = { // not copying domain from site as it is a derived value from page payload.app.publisher = payload.site.publisher; payload.app.ext = payload.site.ext || UNDEFINED; + // We will also need to pass content object in app.content if app object is also set into the config; + // BUT do not use content object from config if content object is present in app as app.content + if (typeof payload.app.content !== 'object') { + payload.app.content = payload.site.content || UNDEFINED; + } delete payload.site; } return { method: 'POST', url: ENDPOINT, - data: JSON.stringify(payload) + data: JSON.stringify(payload), + bidderRequest: bidderRequest }; }, @@ -958,7 +1205,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; @@ -966,6 +1213,7 @@ export const spec = { newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; + _assignRenderer(newBid, request); break; case NATIVE: _parseNativeResponse(bid, newBid); @@ -986,6 +1234,7 @@ export const spec = { newBid.meta.buyerId = bid.ext.advid; } if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; newBid.meta.clickUrl = bid.adomain[0]; } diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index a045bed3e2b..e9d93d79758 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -24,8 +24,9 @@ var adUnits = [ bids: [{ bidder: 'pubmatic', params: { - publisherId: '156209', // required - adSlot: 'pubmatic_test2', // optional + 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. + adSlot: 'pubmatic_test2', // optional, must be a string, not an integer or other js type. pmzoneid: 'zone1, zone11', // optional lat: '40.712775', // optional lon: '-74.005973', // optional diff --git a/modules/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js new file mode 100644 index 00000000000..800ea1cd550 --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.js @@ -0,0 +1,36 @@ +/** + * Analytics Adapter for Pubperf + */ + +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; + +var pubperfAdapter = adapter({ + global: 'pubperf_pbjs', + analyticsType: 'bundle', + handler: 'on' +}); + +pubperfAdapter.originEnableAnalytics = pubperfAdapter.enableAnalytics; + +pubperfAdapter.enableAnalytics = config => { + if (!config || !config.provider || config.provider !== 'pubperf') { + utils.logError('expected config.provider to equal pubperf'); + return; + } + if (!window['pubperf_pbjs']) { + utils.logError( + `Make sure that Pubperf tag from https://www.pubperf.com is included before the Prebid configuration.` + ); + return; + } + pubperfAdapter.originEnableAnalytics(config); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: pubperfAdapter, + code: 'pubperf' +}); + +export default pubperfAdapter; diff --git a/modules/pubperfAnalyticsAdapter.md b/modules/pubperfAnalyticsAdapter.md new file mode 100644 index 00000000000..50ac3691dda --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: Pubperf Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@transfon.com +``` + +# Description + +Transfon's pubperf analytics adaptor allows you to view detailed auction and prebid information in Meridian. Contact support@transfon.com for more information or to sign up for analytics. + +For more information, please visit https://www.pubperf.com. + + +# Sample pubperf tag to be placed before prebid tag + +``` +(function(i, s, o, g, r, a, m, z) {i['pubperf_pbjs'] = r;i[r] = i[r] || function() {z = Array.prototype.slice.call(arguments);z.unshift(+new Date());(i[r].q = i[r].q || []).push(z)}, i[r].t = 1, i[r].l = 1 * new Date();a = s.createElement(o),m = s.getElementsByTagName(o)[0];a.async = 1;a.src = g;m.parentNode.insertBefore(a, m)})(window, document, 'script', 'https://t.pubperf.com/t/b5a635e307.js', 'pubperf_pbjs'); +``` + +# Test Parameters +``` +{ + provider: 'pubperf' +} +``` diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 915aeb58f99..fe217454b88 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -4,7 +4,6 @@ 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(); /**** @@ -17,30 +16,54 @@ const storage = getStorageManager(); pbjs.enableAnalytics({ provider: 'pubwise', options: { - site: 'test-test-test-test', - endpoint: 'https://api.pubwise.io/api/v4/event/add/', + site: 'b1ccf317-a6fc-428d-ba69-0c9c208aa61c' } }); - */ + +Changes in 4.0 Version +4.0.1 - Initial Version for Prebid 4.x, adds activationId, adds additiona testing, removes prebid global in favor of a prebid.version const +4.0.2 - Updates to include dedicated default site to keep everything from getting rate limited + +*/ const analyticsType = 'endpoint'; -const analyticsName = 'PubWise Analytics: '; -let defaultUrl = 'https://api.pubwise.io/api/v4/event/default/'; -let pubwiseVersion = '3.0'; -let pubwiseSchema = 'AVOCET'; -let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v4/event/default/', debug: ''}; +const analyticsName = 'PubWise:'; +const prebidVersion = '$prebid.version$'; +let pubwiseVersion = '4.0.1'; +let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v5/event/add/', debug: null}; let pwAnalyticsEnabled = false; let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''}; +let sessionData = {sessionId: '', activationId: ''}; +let pwNamespace = 'pubwise'; +let pwEvents = []; +let metaData = {}; +let auctionEnded = false; +let sessTimeout = 60 * 30 * 1000; // 30 minutes, G Analytics default session length +let sessName = 'sess_id'; +let sessTimeoutName = 'sess_timeout'; -function markEnabled() { - utils.logInfo(`${analyticsName}Enabled`, configOptions); - pwAnalyticsEnabled = true; +function enrichWithSessionInfo(dataBag) { + try { + // eslint-disable-next-line + // console.log(sessionData); + dataBag['session_id'] = sessionData.sessionId; + dataBag['activation_id'] = sessionData.activationId; + } catch (e) { + dataBag['error_sess'] = 1; + } + + return dataBag; } function enrichWithMetrics(dataBag) { try { + if (window.PREBID_TIMEOUT) { + dataBag['target_timeout'] = window.PREBID_TIMEOUT; + } else { + dataBag['target_timeout'] = 'NA'; + } dataBag['pw_version'] = pubwiseVersion; - dataBag['pbjs_version'] = $$PREBID_GLOBAL$$.version; + dataBag['pbjs_version'] = prebidVersion; dataBag['debug'] = configOptions.debug; } catch (e) { dataBag['error_metric'] = 1; @@ -54,7 +77,7 @@ function enrichWithUTM(dataBag) { try { for (let prop in utmKeys) { utmKeys[prop] = utils.getParameterByName(prop); - if (utmKeys[prop] != '') { + if (utmKeys[prop]) { newUtm = true; dataBag[prop] = utmKeys[prop]; } @@ -62,70 +85,253 @@ function enrichWithUTM(dataBag) { if (newUtm === false) { for (let prop in utmKeys) { - let itemValue = storage.getDataFromLocalStorage(`pw-${prop}`); - if (itemValue.length !== 0) { + let itemValue = storage.getDataFromLocalStorage(setNamespace(prop)); + if (itemValue !== null && typeof itemValue !== 'undefined' && itemValue.length !== 0) { dataBag[prop] = itemValue; } } } else { for (let prop in utmKeys) { - storage.setDataInLocalStorage(`pw-${prop}`, utmKeys[prop]); + storage.setDataInLocalStorage(setNamespace(prop), utmKeys[prop]); } } } catch (e) { - utils.logInfo(`${analyticsName}Error`, e); + pwInfo(`Error`, e); dataBag['error_utm'] = 1; } return dataBag; } -function sendEvent(eventType, data) { - utils.logInfo(`${analyticsName}Event ${eventType} ${pwAnalyticsEnabled}`, data); +function expireUtmData() { + pwInfo(`Session Expiring UTM Data`); + for (let prop in utmKeys) { + storage.removeDataFromLocalStorage(setNamespace(prop)); + } +} + +function enrichWithCustomSegments(dataBag) { + // c_script_type: '', c_slot1: '', c_slot2: '', c_slot3: '', c_slot4: '' + if (configOptions.custom) { + if (configOptions.custom.c_script_type) { + dataBag['c_script_type'] = configOptions.custom.c_script_type; + } + + if (configOptions.custom.c_host) { + dataBag['c_host'] = configOptions.custom.c_host; + } + + if (configOptions.custom.c_slot1) { + dataBag['c_slot1'] = configOptions.custom.c_slot1; + } + + if (configOptions.custom.c_slot2) { + dataBag['c_slot2'] = configOptions.custom.c_slot2; + } + + if (configOptions.custom.c_slot3) { + dataBag['c_slot3'] = configOptions.custom.c_slot3; + } + + if (configOptions.custom.c_slot4) { + dataBag['c_slot4'] = configOptions.custom.c_slot4; + } + } + + return dataBag; +} + +function setNamespace(itemText) { + return pwNamespace.concat('_' + itemText); +} + +function localStorageSessTimeoutName() { + return setNamespace(sessTimeoutName); +} + +function localStorageSessName() { + return setNamespace(sessName); +} + +function extendUserSessionTimeout() { + storage.setDataInLocalStorage(localStorageSessTimeoutName(), Date.now().toString()); +} + +function userSessionID() { + return storage.getDataFromLocalStorage(localStorageSessName()) ? localStorage.getItem(localStorageSessName()) : ''; +} + +function sessionExpired() { + let sessLastTime = storage.getDataFromLocalStorage(localStorageSessTimeoutName()); + return (Date.now() - parseInt(sessLastTime)) > sessTimeout; +} + +function flushEvents() { + if (pwEvents.length > 0) { + let dataBag = {metaData: metaData, eventList: pwEvents.splice(0)}; // put all the events together with the metadata and send + ajax(configOptions.endpoint, (result) => pwInfo(`Result`, result), JSON.stringify(dataBag)); + } +} + +function isIngestedEvent(eventType) { + const ingested = [ + CONSTANTS.EVENTS.AUCTION_INIT, + CONSTANTS.EVENTS.BID_REQUESTED, + CONSTANTS.EVENTS.BID_RESPONSE, + CONSTANTS.EVENTS.BID_WON, + CONSTANTS.EVENTS.BID_TIMEOUT, + CONSTANTS.EVENTS.AD_RENDER_FAILED, + CONSTANTS.EVENTS.TCF2_ENFORCEMENT + ]; + return ingested.indexOf(eventType) !== -1; +} - // put the typical items in the data bag - let dataBag = { - eventType: eventType, - args: data, - target_site: configOptions.site, - pubwiseSchema: pubwiseSchema, - debug: configOptions.debug ? 1 : 0, - }; +function markEnabled() { + pwInfo(`Enabled`, configOptions); + pwAnalyticsEnabled = true; + setInterval(flushEvents, 100); +} + +function pwInfo(info, context) { + utils.logInfo(`${analyticsName} ` + info, context); +} - dataBag = enrichWithMetrics(dataBag); - // for certain events, track additional info - if (eventType == CONSTANTS.EVENTS.AUCTION_INIT) { - dataBag = enrichWithUTM(dataBag); +function filterBidResponse(data) { + let modified = Object.assign({}, data); + // clean up some properties we don't track in public version + if (typeof modified.ad !== 'undefined') { + modified.ad = ''; } + if (typeof modified.adUrl !== 'undefined') { + modified.adUrl = ''; + } + if (typeof modified.adserverTargeting !== 'undefined') { + modified.adserverTargeting = ''; + } + if (typeof modified.ts !== 'undefined') { + modified.ts = ''; + } + // clean up a property to make simpler + if (typeof modified.statusMessage !== 'undefined' && modified.statusMessage === 'Bid returned empty or error response') { + modified.statusMessage = 'eoe'; + } + modified.auctionEnded = auctionEnded; + return modified; +} - ajax(configOptions.endpoint, (result) => utils.logInfo(`${analyticsName}Result`, result), JSON.stringify(dataBag)); +function filterAuctionInit(data) { + let modified = Object.assign({}, data); + + modified.refererInfo = {}; + // handle clean referrer, we only need one + if (typeof modified.bidderRequests !== 'undefined' && typeof modified.bidderRequests[0] !== 'undefined' && typeof modified.bidderRequests[0].refererInfo !== 'undefined') { + modified.refererInfo = modified.bidderRequests[0].refererInfo; + } + + if (typeof modified.adUnitCodes !== 'undefined') { + delete modified.adUnitCodes; + } + if (typeof modified.adUnits !== 'undefined') { + delete modified.adUnits; + } + if (typeof modified.bidderRequests !== 'undefined') { + delete modified.bidderRequests; + } + if (typeof modified.bidsReceived !== 'undefined') { + delete modified.bidsReceived; + } + if (typeof modified.config !== 'undefined') { + delete modified.config; + } + if (typeof modified.noBids !== 'undefined') { + delete modified.noBids; + } + if (typeof modified.winningBids !== 'undefined') { + delete modified.winningBids; + } + + return modified; } -let pubwiseAnalytics = Object.assign(adapter( - { - defaultUrl, - analyticsType - }), -{ +let pubwiseAnalytics = Object.assign(adapter({analyticsType}), { // Override AnalyticsAdapter functions by supplying custom methods track({eventType, args}) { - sendEvent(eventType, args); + this.handleEvent(eventType, args); } }); +pubwiseAnalytics.handleEvent = function(eventType, data) { + // we log most events, but some are information + if (isIngestedEvent(eventType)) { + pwInfo(`Emitting Event ${eventType} ${pwAnalyticsEnabled}`, data); + + // record metadata + metaData = { + target_site: configOptions.site, + debug: configOptions.debug ? 1 : 0, + }; + metaData = enrichWithSessionInfo(metaData); + metaData = enrichWithMetrics(metaData); + metaData = enrichWithUTM(metaData); + metaData = enrichWithCustomSegments(metaData); + + // add data on init to the metadata container + if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + data = filterAuctionInit(data); + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + data = filterBidResponse(data); + } + + // add all ingested events + pwEvents.push({ + eventType: eventType, + args: data + }); + } else { + pwInfo(`Skipping Event ${eventType} ${pwAnalyticsEnabled}`, data); + } + + // once the auction ends, or the event is a bid won send events + if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) { + flushEvents(); + } +} + +pubwiseAnalytics.storeSessionID = function (userSessID) { + storage.setDataInLocalStorage(localStorageSessName(), userSessID); + pwInfo(`New Session Generated`, 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(); + expireUtmData(); + this.storeSessionID(generatedId); + sessionData.sessionId = generatedId; + } + // eslint-disable-next-line + // console.log('ensured session'); + extendUserSessionTimeout(); +}; + pubwiseAnalytics.adapterEnableAnalytics = pubwiseAnalytics.enableAnalytics; pubwiseAnalytics.enableAnalytics = function (config) { - if (config.options.debug === undefined) { - config.options.debug = utils.debugTurnedOn(); + 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 = config.options; markEnabled(); + sessionData.activationId = utils.generateUUID(); + this.ensureSession(); pubwiseAnalytics.adapterEnableAnalytics(config); }; adapterManager.registerAnalyticsAdapter({ adapter: pubwiseAnalytics, - code: 'pubwise' + code: 'pubwise', + gvlid: 842 }); export default pubwiseAnalytics; diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js new file mode 100644 index 00000000000..f450a8bede8 --- /dev/null +++ b/modules/pubwiseBidAdapter.js @@ -0,0 +1,777 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +const VERSION = '0.1.0'; +const GVLID = 842; +const NET_REVENUE = true; +const UNDEFINED = undefined; +const DEFAULT_CURRENCY = 'USD'; +const AUCTION_TYPE = 1; +const BIDDER_CODE = 'pwbid'; +const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; +const DEFAULT_WIDTH = 0; +const DEFAULT_HEIGHT = 0; +const PREBID_NATIVE_HELP_LINK = 'https://prebid.org/dev-docs/show-native-ads.html'; +// const USERSYNC_URL = '//127.0.0.1:8080/usersync' + +const CUSTOM_PARAMS = { + 'gender': '', // User gender + 'yob': '', // User year of birth + 'lat': '', // User location - Latitude + 'lon': '', // User Location - Longitude +}; + +// rtb native types are meant to be dynamic and extendable +// the extendable data asset types are nicely aligned +// in practice we set an ID that is distinct for each real type of return +const NATIVE_ASSETS = { + 'TITLE': { ID: 1, KEY: 'title', TYPE: 0 }, + 'IMAGE': { ID: 2, KEY: 'image', TYPE: 0 }, + 'ICON': { ID: 3, KEY: 'icon', TYPE: 0 }, + 'SPONSOREDBY': { ID: 4, KEY: 'sponsoredBy', TYPE: 1 }, + 'BODY': { ID: 5, KEY: 'body', TYPE: 2 }, + 'CLICKURL': { ID: 6, KEY: 'clickUrl', TYPE: 0 }, + 'VIDEO': { ID: 7, KEY: 'video', TYPE: 0 }, + 'EXT': { ID: 8, KEY: 'ext', TYPE: 0 }, + 'DATA': { ID: 9, KEY: 'data', TYPE: 0 }, + 'LOGO': { ID: 10, KEY: 'logo', TYPE: 0 }, + 'SPONSORED': { ID: 11, KEY: 'sponsored', TYPE: 1 }, + 'DESC': { ID: 12, KEY: 'data', TYPE: 2 }, + 'RATING': { ID: 13, KEY: 'rating', TYPE: 3 }, + 'LIKES': { ID: 14, KEY: 'likes', TYPE: 4 }, + 'DOWNLOADS': { ID: 15, KEY: 'downloads', TYPE: 5 }, + 'PRICE': { ID: 16, KEY: 'price', TYPE: 6 }, + 'SALEPRICE': { ID: 17, KEY: 'saleprice', TYPE: 7 }, + 'PHONE': { ID: 18, KEY: 'phone', TYPE: 8 }, + 'ADDRESS': { ID: 19, KEY: 'address', TYPE: 9 }, + 'DESC2': { ID: 20, KEY: 'desc2', TYPE: 10 }, + 'DISPLAYURL': { ID: 21, KEY: 'displayurl', TYPE: 11 }, + 'CTA': { ID: 22, KEY: 'cta', TYPE: 12 } +}; + +const NATIVE_ASSET_IMAGE_TYPE = { + 'ICON': 1, + 'LOGO': 2, + 'IMAGE': 3 +} + +// to render any native unit we have to have a few items +const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ + { + id: NATIVE_ASSETS.SPONSOREDBY.ID, + required: true, + data: { + type: 1 + } + }, + { + id: NATIVE_ASSETS.TITLE.ID, + required: true, + }, + { + id: NATIVE_ASSETS.IMAGE.ID, + required: true, + } +] + +let isInvalidNativeRequest = false +let NATIVE_ASSET_ID_TO_KEY_MAP = {}; +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 }); +// key -> asset +utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, NATIVE], + /** + * 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) { + // siteId is required + if (bid.params && bid.params.siteId) { + // it must be a string + if (!utils.isStr(bid.params.siteId)) { + _logWarn('siteId is required for bid', bid); + return false; + } + } else { + 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) { + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = _initConf(refererInfo); + var payload = _createOrtbTemplate(conf); + var bidCurrency = ''; + var bid; + var blockedIabCategories = []; + + validBidRequests.forEach(originalBid => { + bid = utils.deepClone(originalBid); + bid.params.adSlot = bid.params.adSlot || ''; + _parseAdSlot(bid); + + conf = _handleCustomParams(bid.params, conf); + conf.transactionId = bid.transactionId; + bidCurrency = bid.params.currency || UNDEFINED; + bid.params.currency = bidCurrency; + + if (bid.params.hasOwnProperty('bcat') && utils.isArray(bid.params.bcat)) { + blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); + } + + var impObj = _createImpressionObject(bid, conf); + if (impObj) { + payload.imp.push(impObj); + } + }); + + // no payload imps, no rason to continue + if (payload.imp.length == 0) { + return; + } + + // test bids can also be turned on here + if (window.location.href.indexOf('pubwiseTestBid=true') !== -1) { + payload.test = 1; + } + + if (bid.params.isTest) { + payload.test = Number(bid.params.isTest) // should be 1 or 0 + } + payload.site.publisher.id = bid.params.siteId.trim(); + payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); + payload.user.geo = {}; + payload.user.geo.lat = _parseSlotParam('lat', conf.lat); + payload.user.geo.lon = _parseSlotParam('lon', conf.lon); + payload.user.yob = _parseSlotParam('yob', conf.yob); + payload.device.geo = payload.user.geo; + payload.site.page = payload.site.page.trim(); + payload.site.domain = _getDomainFromURL(payload.site.page); + + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + + // merge the device from config.getConfig('device') + if (typeof config.getConfig('device') === 'object') { + payload.device = Object.assign(payload.device, config.getConfig('device')); + } + + // passing transactionId in source.tid + utils.deepSetValue(payload, 'source.tid', conf.transactionId); + + // schain + if (validBidRequests[0].schain) { + utils.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)); + } + + // ccpa on the root object + if (bidderRequest && bidderRequest.uspConsent) { + utils.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); + } + + var options = {contentType: 'text/plain'} + + _logInfo('buildRequests payload', payload); + _logInfo('buildRequests bidderRequest', bidderRequest); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload, + options: options, + bidderRequest: 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 (response, request) { + const bidResponses = []; + var respCur = DEFAULT_CURRENCY; + _logInfo('interpretResponse request', request); + let parsedRequest = request.data; // not currently stringified + // let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + + // try { + if (response.body && response.body.seatbid && utils.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) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + currency: respCur, + netRevenue: NET_REVENUE, + ttl: 300, + ad: bid.adm, + pw_seat: seatbidder.seat || null, + pw_dspid: bid.ext && bid.ext.dspid ? bid.ext.dspid : null, + partnerImpId: bid.id || '' // partner impression Id + }; + if (parsedRequest.imp && parsedRequest.imp.length > 0) { + parsedRequest.imp.forEach(req => { + if (bid.impid === req.id) { + _checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case NATIVE: + _parseNativeResponse(bid, newBid); + break; + } + } + }); + } + + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; + newBid.meta.clickUrl = bid.adomain[0]; + } + + bidResponses.push(newBid); + }); + }); + } + // } catch (error) { + // _logError(error); + // } + return bidResponses; + } +} + +function _checkMediaType(adm, newBid) { + // Create a regex here to check the strings + var admJSON = ''; + if (adm.indexOf('"ver":') >= 0) { + try { + admJSON = JSON.parse(adm.replace(/\\/g, '')); + if (admJSON && admJSON.assets) { + newBid.mediaType = NATIVE; + } + } catch (e) { + _logWarn('Error: Cannot parse native reponse for ad response: ' + adm); + } + } else { + newBid.mediaType = BANNER; + } +} + +function _parseNativeResponse(bid, newBid) { + newBid.native = {}; + if (bid.hasOwnProperty('adm')) { + var adm = ''; + try { + adm = JSON.parse(bid.adm.replace(/\\/g, '')); + } catch (ex) { + _logWarn('Error: Cannot parse native reponse for ad response: ' + newBid.adm); + return; + } + if (adm && adm.assets && adm.assets.length > 0) { + newBid.mediaType = NATIVE; + for (let i = 0, len = adm.assets.length; i < len; i++) { + switch (adm.assets[i].id) { + case NATIVE_ASSETS.TITLE.ID: + newBid.native.title = adm.assets[i].title && adm.assets[i].title.text; + break; + case NATIVE_ASSETS.IMAGE.ID: + newBid.native.image = { + url: adm.assets[i].img && adm.assets[i].img.url, + height: adm.assets[i].img && adm.assets[i].img.h, + width: adm.assets[i].img && adm.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.ICON.ID: + newBid.native.icon = { + url: adm.assets[i].img && adm.assets[i].img.url, + height: adm.assets[i].img && adm.assets[i].img.h, + width: adm.assets[i].img && adm.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.SPONSOREDBY.ID: + case NATIVE_ASSETS.BODY.ID: + case NATIVE_ASSETS.LIKES.ID: + case NATIVE_ASSETS.DOWNLOADS.ID: + case NATIVE_ASSETS.PRICE: + case NATIVE_ASSETS.SALEPRICE.ID: + case NATIVE_ASSETS.PHONE.ID: + case NATIVE_ASSETS.ADDRESS.ID: + case NATIVE_ASSETS.DESC2.ID: + case NATIVE_ASSETS.CTA.ID: + case NATIVE_ASSETS.RATING.ID: + case NATIVE_ASSETS.DISPLAYURL.ID: + newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.assets[i].id]] = adm.assets[i].data && adm.assets[i].data.value; + break; + } + } + newBid.clickUrl = adm.link && adm.link.url; + newBid.clickTrackers = (adm.link && adm.link.clicktrackers) || []; + newBid.impressionTrackers = adm.imptrackers || []; + newBid.jstracker = adm.jstracker || []; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; + } + } + } +} + +function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + +function _handleCustomParams(params, conf) { + var key, value, entry; + for (key in CUSTOM_PARAMS) { + if (CUSTOM_PARAMS.hasOwnProperty(key)) { + value = params[key]; + if (value) { + entry = CUSTOM_PARAMS[key]; + + if (typeof entry === 'object') { + // will be used in future when we want to + // process a custom param before using + // 'keyname': {f: function() {}} + value = entry.f(value, conf); + } + + if (utils.isStr(value)) { + conf[key] = value; + } else { + _logWarn('Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + } + } + } + } + return conf; +} + +function _createOrtbTemplate(conf) { + return { + id: '' + new Date().getTime(), + at: AUCTION_TYPE, + cur: [DEFAULT_CURRENCY], + imp: [], + site: { + page: conf.pageURL, + ref: conf.refURL, + publisher: {} + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + user: {}, + ext: { + version: VERSION + } + }; +} + +function _createImpressionObject(bid, conf) { + var impObj = {}; + var bannerObj; + var nativeObj = {}; + var mediaTypes = ''; + + impObj = { + id: bid.bidId, + tagid: bid.params.adUnit || undefined, + bidfloor: _parseSlotParam('bidFloor', bid.params.bidFloor), // capitalization dicated by 3.2.4 spec + secure: 1, + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY // capitalization dicated by 3.2.4 spec + }; + + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bannerObj = _createBannerRequest(bid); + if (bannerObj !== UNDEFINED) { + impObj.banner = bannerObj; + } + break; + case NATIVE: + nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); + if (!isInvalidNativeRequest) { + impObj.native = nativeObj; + } else { + _logWarn('Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + } + break; + } + } + } else { + _logWarn('MediaTypes are Required for all Adunit Configs', bid) + } + + _addFloorFromFloorModule(impObj, bid); + + return impObj.hasOwnProperty(BANNER) || + impObj.hasOwnProperty(NATIVE) ? impObj : UNDEFINED; +} + +function _parseSlotParam(paramName, paramValue) { + if (!utils.isStr(paramValue)) { + paramValue && _logWarn('Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + return UNDEFINED; + } + + switch (paramName) { + case 'bidFloor': + return parseFloat(paramValue) || UNDEFINED; + case 'lat': + return parseFloat(paramValue) || UNDEFINED; + case 'lon': + return parseFloat(paramValue) || UNDEFINED; + case 'yob': + return parseInt(paramValue) || UNDEFINED; + default: + return paramValue; + } +} + +function _parseAdSlot(bid) { + _logInfo('parseAdSlot bid', bid) + bid.params.adUnit = ''; + bid.params.width = 0; + bid.params.height = 0; + bid.params.adSlot = _cleanSlotName(bid.params.adSlot); + + if (bid.hasOwnProperty('mediaTypes')) { + if (bid.mediaTypes.hasOwnProperty(BANNER) && + bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes + var i = 0; + var sizeArray = []; + for (;i < bid.mediaTypes.banner.sizes.length; i++) { + if (bid.mediaTypes.banner.sizes[i].length === 2) { // sizes[i].length will not be 2 in case where size is set as fluid, we want to skip that entry + sizeArray.push(bid.mediaTypes.banner.sizes[i]); + } + } + bid.mediaTypes.banner.sizes = sizeArray; + if (bid.mediaTypes.banner.sizes.length >= 1) { + // if there is more than one size then pop one onto the banner params width + // pop the first into the params, then remove it from mediaTypes + bid.params.width = bid.mediaTypes.banner.sizes[0][0]; + bid.params.height = bid.mediaTypes.banner.sizes[0][1]; + bid.mediaTypes.banner.sizes = bid.mediaTypes.banner.sizes.splice(1, bid.mediaTypes.banner.sizes.length - 1); + } + } + } else { + _logWarn('MediaTypes are Required for all Adunit Configs', bid) + } +} + +function _cleanSlotName(slotName) { + if (utils.isStr(slotName)) { + return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); + } + return ''; +} + +function _initConf(refererInfo) { + return { + pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, + refURL: window.document.referrer + }; +} + +function _commonNativeRequestObject(nativeAsset, params) { + var key = nativeAsset.KEY; + return { + id: nativeAsset.ID, + required: params[key].required ? 1 : 0, + data: { + type: nativeAsset.TYPE, + len: params[key].len, + ext: params[key].ext + } + }; +} + +function _addFloorFromFloorModule(impObj, bid) { + let bidFloor = -1; // indicates no floor + + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function' && !config.getConfig('pubwise.disableFloors')) { + [BANNER, 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)) + } + } + }); + } + + // get highest, if none then take the default -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor) + } + + // assign if it has a valid floor - > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); +} + +function _createNativeRequest(params) { + var nativeRequestObject = { + assets: [] + }; + for (var key in params) { + if (params.hasOwnProperty(key)) { + var assetObj = {}; + if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { + switch (key) { + case NATIVE_ASSETS.TITLE.KEY: + if (params[key].len || params[key].length) { + assetObj = { + id: NATIVE_ASSETS.TITLE.ID, + required: params[key].required ? 1 : 0, + title: { + len: params[key].len || params[key].length, + ext: params[key].ext + } + }; + } else { + _logWarn('Error: Title Length is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSETS.IMAGE.KEY: + if (params[key].sizes && params[key].sizes.length > 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 || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), + hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), + mimes: params[key].mimes, + ext: params[key].ext, + } + }; + } else { + _logWarn('Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSETS.ICON.KEY: + if (params[key].sizes && params[key].sizes.length > 0) { + assetObj = { + id: NATIVE_ASSETS.ICON.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.ICON, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + } + }; + } else { + _logWarn('Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + }; + break; + case NATIVE_ASSETS.VIDEO.KEY: + assetObj = { + id: NATIVE_ASSETS.VIDEO.ID, + required: params[key].required ? 1 : 0, + video: { + minduration: params[key].minduration, + maxduration: params[key].maxduration, + protocols: params[key].protocols, + mimes: params[key].mimes, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSETS.EXT.KEY: + assetObj = { + id: NATIVE_ASSETS.EXT.ID, + required: params[key].required ? 1 : 0, + }; + break; + case NATIVE_ASSETS.LOGO.KEY: + assetObj = { + id: NATIVE_ASSETS.LOGO.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.LOGO, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED) + } + }; + break; + case NATIVE_ASSETS.SPONSOREDBY.KEY: + case NATIVE_ASSETS.BODY.KEY: + case NATIVE_ASSETS.RATING.KEY: + case NATIVE_ASSETS.LIKES.KEY: + case NATIVE_ASSETS.DOWNLOADS.KEY: + case NATIVE_ASSETS.PRICE.KEY: + case NATIVE_ASSETS.SALEPRICE.KEY: + case NATIVE_ASSETS.PHONE.KEY: + case NATIVE_ASSETS.ADDRESS.KEY: + case NATIVE_ASSETS.DESC2.KEY: + case NATIVE_ASSETS.DISPLAYURL.KEY: + case NATIVE_ASSETS.CTA.KEY: + assetObj = _commonNativeRequestObject(NATIVE_ASSET_KEY_TO_ASSET_MAP[key], params); + break; + } + } + } + if (assetObj && assetObj.id) { + nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + } + }; + + // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image + // if any of these are missing from the request then request will not be sent + var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; + var presentrequiredAssetCount = 0; + NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { + var lengthOfExistingAssets = nativeRequestObject.assets.length; + for (var i = 0; i < lengthOfExistingAssets; i++) { + if (ele.id == nativeRequestObject.assets[i].id) { + presentrequiredAssetCount++; + break; + } + } + }); + if (requiredAssetCount == presentrequiredAssetCount) { + isInvalidNativeRequest = false; + } else { + isInvalidNativeRequest = true; + } + return nativeRequestObject; +} + +function _createBannerRequest(bid) { + var sizes = bid.mediaTypes.banner.sizes; + var format = []; + var bannerObj; + if (sizes !== UNDEFINED && utils.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; + _logWarn('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); + bannerObj.h = parseInt(sizes[0][1], 10); + sizes = sizes.splice(1, sizes.length - 1); + } + } else { + bannerObj.w = bid.params.width; + bannerObj.h = bid.params.height; + } + if (sizes.length > 0) { + format = []; + sizes.forEach(function (size) { + if (size.length > 1) { + format.push({ w: size[0], h: size[1] }); + } + }); + if (format.length > 0) { + bannerObj.format = format; + } + } + bannerObj.pos = 0; + bannerObj.topframe = utils.inIframe() ? 0 : 1; + } else { + _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + bannerObj = UNDEFINED; + } + return bannerObj; +} + +// various error levels are not always used +// eslint-disable-next-line no-unused-vars +function _logMessage(textValue, objectValue) { + utils.logMessage('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logInfo(textValue, objectValue) { + utils.logInfo('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logWarn(textValue, objectValue) { + utils.logWarn('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logError(textValue, objectValue) { + utils.logError('PubWise: ' + textValue, objectValue); +} + +// function _decorateLog() { +// arguments[0] = 'PubWise' + arguments[0]; +// return arguments +// } + +// these are exported only for testing so maintaining the JS convention of _ to indicate the intent +export { + _checkMediaType, + _parseAdSlot +} + +registerBidder(spec); diff --git a/modules/pubwiseBidAdapter.md b/modules/pubwiseBidAdapter.md new file mode 100644 index 00000000000..8cf38a63913 --- /dev/null +++ b/modules/pubwiseBidAdapter.md @@ -0,0 +1,78 @@ +# Overview + +``` +Module Name: PubWise Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@pubwise.io +``` + +# Description + +Connects to PubWise exchange for bids. + +# Sample Banner Ad Unit: For Publishers + +With isTest parameter the system will respond in whatever dimensions provided. + +## Params + + + +## Banner +``` +var adUnits = [ + { + code: "div-gpt-ad-1460505748561-0", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'pubwise', + params: { + siteId: "xxxxxx", + isTest: true + } + }] + } +] +``` +## Native +``` +var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-1', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'pubwise', + params: { + siteId: "xxxxxx", + isTest: true, + }, + }] + } +] +``` + diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js new file mode 100644 index 00000000000..c7b63d3f7f7 --- /dev/null +++ b/modules/pubxBidAdapter.js @@ -0,0 +1,99 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.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' +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (!(bid.params.sid)) { + return false; + } else { return true } + }, + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + const bidId = bidRequest.bidId; + const params = bidRequest.params; + const sid = params.sid; + const payload = { + sid: sid + }; + return { + id: bidId, + method: 'GET', + url: BID_ENDPOINT, + data: payload, + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const body = serverResponse.body; + const bidResponses = []; + if (body.cid) { + const bidResponse = { + requestId: bidRequest.id, + cpm: body.cpm, + currency: body.currency, + width: body.width, + height: body.height, + creativeId: body.cid, + netRevenue: true, + ttl: body.TTL, + ad: body.adm + }; + if (body.adomains) { + utils.deepSetValue(bidResponse, 'meta.advertiserDomains', Array.isArray(body.adomains) ? body.adomains : [body.adomains]); + } + bidResponses.push(bidResponse); + } else {}; + return bidResponses; + }, + /** + * Determine which user syncs should occur + * @param {object} syncOptions + * @param {array} serverResponses + * @returns {array} User sync pixels + */ + getUserSyncs: function (syncOptions, serverResponses) { + const kwTag = document.getElementsByName('keywords'); + let kwString = ''; + let kwEnc = ''; + let titleContent = !!document.title && document.title; + let titleEnc = ''; + let descContent = !!document.getElementsByName('description') && !!document.getElementsByName('description')[0] && document.getElementsByName('description')[0].content; + let descEnc = ''; + const pageUrl = location.href.replace(/\?.*$/, ''); + const pageEnc = encodeURIComponent(pageUrl); + const refUrl = document.referrer.replace(/\?.*$/, ''); + const refEnc = encodeURIComponent(refUrl); + if (kwTag.length) { + const kwContents = kwTag[0].content; + if (kwContents.length > 20) { + const kwArray = kwContents.substr(0, 20).split(','); + kwArray.pop(); + kwString = kwArray.join(); + } else { + kwString = kwContents; + } + kwEnc = encodeURIComponent(kwString) + } else { } + if (titleContent) { + if (titleContent.length > 30) { + titleContent = titleContent.substr(0, 30); + } else {}; + titleEnc = encodeURIComponent(titleContent); + } else { }; + if (descContent) { + if (descContent.length > 60) { + descContent = descContent.substr(0, 60); + } else {}; + descEnc = encodeURIComponent(descContent); + } else { }; + return (syncOptions.iframeEnabled) ? [{ + type: 'iframe', + url: USER_SYNC_URL + '?pkw=' + kwEnc + '&pd=' + descEnc + '&pu=' + pageEnc + '&pref=' + refEnc + '&pt=' + titleEnc + }] : []; + } +} +registerBidder(spec); diff --git a/modules/pubxBidAdapter.md b/modules/pubxBidAdapter.md new file mode 100644 index 00000000000..da7d960c831 --- /dev/null +++ b/modules/pubxBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: pubx Bid Adapter + +Maintainer: x@pub-x.io + +# Description + +Module that connects to Pub-X's demand sources +Supported MediaTypes: banner only + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test', + mediaTypes: { + banner: { + sizes: [300,250] + } + }, + bids: [ + { + bidder: 'pubx', + params: { + sid: 'eDMR' //ID should be provided by Pub-X + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js new file mode 100644 index 00000000000..98b4dec1dca --- /dev/null +++ b/modules/pubxaiAnalyticsAdapter.js @@ -0,0 +1,183 @@ +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'; +const pubxaiAnalyticsVersion = 'v1.1.0'; +const defaultHost = 'api.pbxai.com'; +const auctionPath = '/analytics/auction'; +const winningBidPath = '/analytics/bidwon'; + +let initOptions; +let auctionTimestamp; +let auctionCache = []; +let events = { + bids: [], + floorDetail: {}, + pageDetail: {}, + deviceDetail: {} +}; + +var pubxaiAnalyticsAdapter = Object.assign(adapter( + { + emptyUrl, + analyticsType + }), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + args.forEach(item => { mapBidResponse(item, 'timeout'); }); + } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + events.auctionInit = args; + events.floorDetail = {}; + events.bids = []; + const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (typeof floorData !== 'undefined') { + Object.assign(events.floorDetail, floorData); + } + auctionTimestamp = args.timestamp; + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + mapBidResponse(args, 'response'); + } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + send({ + winningBid: mapBidResponse(args, 'bidwon') + }, 'bidwon'); + } + } + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + send(events, 'auctionEnd'); + } + } +}); + +function mapBidResponse(bidResponse, status) { + if (typeof bidResponse !== 'undefined') { + let bid = { + adUnitCode: bidResponse.adUnitCode, + gptSlotCode: utils.getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, + auctionId: bidResponse.auctionId, + bidderCode: bidResponse.bidder, + cpm: bidResponse.cpm, + creativeId: bidResponse.creativeId, + currency: bidResponse.currency, + floorData: bidResponse.floorData, + mediaType: bidResponse.mediaType, + netRevenue: bidResponse.netRevenue, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp, + status: bidResponse.status, + statusMessage: bidResponse.statusMessage, + timeToRespond: bidResponse.timeToRespond, + transactionId: bidResponse.transactionId + }; + if (status !== 'bidwon') { + Object.assign(bid, { + bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, + renderStatus: status === 'timeout' ? 3 : 2, + sizes: utils.parseSizesInput(bidResponse.size).toString(), + }); + events.bids.push(bid); + } else { + Object.assign(bid, { + bidId: bidResponse.requestId, + floorProvider: events.floorDetail ? events.floorDetail.floorProvider : null, + isWinningBid: true, + placementId: bidResponse.params ? utils.deepAccess(bidResponse, 'params.0.placementId') : null, + renderedSize: bidResponse.size, + renderStatus: 4 + }); + return bid; + } + } +} + +export function getDeviceType() { + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 'tablet'; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 'mobile'; + } + return 'desktop'; +} + +export function getBrowser() { + if (/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)) return 'Chrome'; + else if (navigator.userAgent.match('CriOS')) return 'Chrome'; + else if (/Firefox/.test(navigator.userAgent)) return 'Firefox'; + else if (/Edg/.test(navigator.userAgent)) return 'Microsoft Edge'; + else if (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor)) return 'Safari'; + else if (/Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent)) return 'Internet Explorer'; + else return 'Others'; +} + +export function getOS() { + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; +} + +// add sampling rate +pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { + return (Math.floor((Math.random() * samplingRate + 1)) === parseInt(samplingRate)); +} + +function send(data, status) { + if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { + let location = utils.getWindowLocation(); + data.initOptions = initOptions; + 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.initOptions.auctionId = data.auctionInit.auctionId; + delete data.auctionInit; + } + data.deviceDetail = {}; + Object.assign(data.deviceDetail, { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser() + }); + let pubxaiAnalyticsRequestUrl = utils.buildUrl({ + protocol: 'https', + hostname: (initOptions && initOptions.hostName) || defaultHost, + pathname: status == 'bidwon' ? winningBidPath : auctionPath, + search: { + auctionTimestamp: auctionTimestamp, + pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + if (status == 'bidwon') { + ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); + } else if (status == 'auctionEnd' && auctionCache.indexOf(data.initOptions.auctionId) === -1) { + ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/json' }); + auctionCache.push(data.initOptions.auctionId); + } + } +} + +pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; +pubxaiAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + pubxaiAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: pubxaiAnalyticsAdapter, + code: 'pubxai' +}); + +export default pubxaiAnalyticsAdapter; diff --git a/modules/pubxaiAnalyticsAdapter.md b/modules/pubxaiAnalyticsAdapter.md new file mode 100644 index 00000000000..112329fc171 --- /dev/null +++ b/modules/pubxaiAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview +Module Name: PubX.io Analytics Adapter +Module Type: Analytics Adapter +Maintainer: phaneendra@pubx.ai + +# Description + +Analytics adapter for prebid provided by Pubx.ai. Contact alex@pubx.ai for information. + +# Test Parameters + +``` +{ + provider: 'pubxai', + options : { + pubxId: 'xxx', + hostName: 'example.com', + samplingRate: 1 + } +} +``` +Property | Data Type | Is required? | Description |Example +:-----:|:-----:|:-----:|:-----:|:-----: +pubxId|string|Yes | A unique identifier provided by PubX.ai to indetify publishers. |`"a9d48e2f-24ec-4ec1-b3e2-04e32c3aeb03"` +hostName|string|No|hostName is provided by Pubx.ai. |`"https://example.com"` +samplingRate |number |No|How often the sampling must be taken. |`2` - (sample one in two cases) \ `3` - (sample one in three cases) + | | | | \ No newline at end of file diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 7bfa8686728..5f8f096926b 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -119,12 +119,10 @@ 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]['native']) { - bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); - bid.mediaType = 'native'; - } else if (idToImpMap[id].video) { + 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')); @@ -133,10 +131,13 @@ function bidResponseAvailable(request, response) { bid.mediaType = 'video'; bid.width = idToBidMap[id].w; bid.height = idToBidMap[id].h; - } else { + } else if (idToImpMap[id].banner) { bid.ad = idToBidMap[id].adm; bid.width = idToBidMap[id].w || idToImpMap[id].banner.w; bid.height = idToBidMap[id].h || idToImpMap[id].banner.h; + } else if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + bid.mediaType = 'native'; } bids.push(bid); } @@ -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), }; } @@ -165,12 +166,12 @@ function impression(slot) { function banner(slot) { const sizes = parseSizes(slot); const size = adSize(slot, sizes); - return (slot.nativeParams || slot.params.video) ? null : { + return (slot.mediaTypes && slot.mediaTypes.banner) ? { w: size[0], h: size[1], battr: slot.params.battr, format: sizes - }; + } : null; } /** @@ -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; } @@ -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, bidRequest.userId.id5id, 'id5-sync.com'); - addExternalUserId(ext.eids, bidRequest.userId.parrableid, 'parrable.com'); + addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo.com'); + addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'liveramp.com'); + 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.fabrickId, 'neustar.biz'); + addExternalUserId(ext.eids, utils.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, utils.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 (utils.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..9baff4d1533 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -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 91022d70df9..ecb5ad3176e 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import find from 'core-js-pure/features/array/find.js'; @@ -18,35 +19,38 @@ export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; export const QUANTCAST_TTL = 4; export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; +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 = utils.deepAccess(bid, 'mediaTypes.video') || {}; + const videoInParams = utils.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 }; @@ -85,11 +89,6 @@ function checkTCFv1(vendorData) { } function checkTCFv2(tcData) { - if (tcData.purposeOneTreatment && tcData.publisherCC === 'DE') { - // special purpose 1 treatment for Germany - return true; - } - let restrictions = tcData.publisher ? tcData.publisher.restrictions : {}; let qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT] ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] @@ -106,15 +105,21 @@ function checkTCFv2(tcData) { return !!(vendorConsent && purposeConsent); } +function getQuantcastFPA() { + let fpa = storage.getCookie(QUANTCAST_FPA) + return fpa || '' +} + +let hasUserSynced = false; + /** * The documentation for Prebid.js Adapter 1.0 can be found at link below, * http://prebid.org/dev-docs/bidder-adapter-1.html */ export const spec = { code: BIDDER_CODE, - GVLID: 11, + GVLID: QUANTCAST_VENDOR_ID, supportedMediaTypes: ['banner', 'video'], - hasUserSynced: false, /** * Verify the `AdUnits.bids` response with `true` for valid request and `false` @@ -193,7 +198,8 @@ export const spec = { uspSignal: uspConsent ? 1 : 0, uspConsent, coppa: config.getConfig('coppa') === true ? 1 : 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: getQuantcastFPA() }; const data = JSON.stringify(requestData); @@ -242,7 +248,7 @@ export const spec = { } 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, @@ -265,6 +271,11 @@ export const spec = { result['dealId'] = dealId; } + if (meta !== undefined && meta.advertiserDomains && utils.isArray(meta.advertiserDomains)) { + result.meta = {}; + result.meta.advertiserDomains = meta.advertiserDomains; + } + return result; }); @@ -276,7 +287,7 @@ export const spec = { }, getUserSyncs(syncOptions, serverResponses) { const syncs = [] - if (!this.hasUserSynced && syncOptions.pixelEnabled) { + if (!hasUserSynced && syncOptions.pixelEnabled) { const responseWithUrl = find(serverResponses, serverResponse => utils.deepAccess(serverResponse.body, 'userSync.url') ); @@ -288,12 +299,12 @@ export const spec = { url: url }); } - this.hasUserSynced = true; + hasUserSynced = true; } return syncs; }, resetUserSync() { - this.hasUserSynced = false; + hasUserSynced = false; } }; diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js new file mode 100644 index 00000000000..7d82be884da --- /dev/null +++ b/modules/quantcastIdSystem.js @@ -0,0 +1,224 @@ +/** + * This module adds QuantcastID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/quantcastIdSystem + * @requires module:modules/userId + */ + +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 = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'quantcastId', + + /** + * Vendor id of Quantcast + * @type {Number} + */ + gvlid: QUANTCAST_VENDOR_ID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{quantcastId: string} | undefined} + */ + decode(value) { + return value; + }, + + /** + * read Quantcast first party cookie and pass it along in quantcastId + * @function + * @returns {{id: {quantcastId: string} | undefined}}} + */ + 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 } + } +}; + +submodule('userId', quantcastIdSubmodule); 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/quantumBidAdapter.js b/modules/quantumBidAdapter.js deleted file mode 100644 index f5da6e022f1..00000000000 --- a/modules/quantumBidAdapter.js +++ /dev/null @@ -1,320 +0,0 @@ -import * as utils from '../src/utils.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'quantum'; -const ENDPOINT_URL = 'https://s.sspqns.com/hb'; -export const spec = { - code: BIDDER_CODE, - aliases: ['quantx', 'qtx'], // short code - supportedMediaTypes: [BANNER, NATIVE], - - /** - * 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.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 (bidRequests, bidderRequest) { - return bidRequests.map(bid => { - const qtxRequest = {}; - let bidId = ''; - - const params = bid.params; - let placementId = params.placementId; - - let devEnpoint = false; - if (params.useDev && params.useDev === '1') { - devEnpoint = 'https://sdev.sspqns.com/hb'; - } - let renderMode = 'native'; - for (let i = 0; i < bid.sizes.length; i++) { - if (bid.sizes[i][0] > 1 && bid.sizes[i][1] > 1) { - renderMode = 'banner'; - break; - } - } - - let mediaType = (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) ? 'native' : 'banner'; - - if (mediaType === 'native') { - renderMode = 'native'; - } - - if (!bidId) { - bidId = bid.bidId; - } - qtxRequest.auid = placementId; - - if (bidderRequest && bidderRequest.gdprConsent) { - qtxRequest.quantx_user_consent_string = bidderRequest.gdprConsent.consentString; - qtxRequest.quantx_gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - }; - - const url = devEnpoint || ENDPOINT_URL; - - return { - method: 'GET', - bidId: bidId, - sizes: bid.sizes, - mediaType: mediaType, - renderMode: renderMode, - url: url, - 'data': qtxRequest, - 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) { - const serverBody = serverResponse.body; - const bidResponses = []; - let responseCPM; - let bid = {}; - let id = bidRequest.bidId; - - if (serverBody.price && serverBody.price !== 0) { - responseCPM = parseFloat(serverBody.price); - - bid.creativeId = serverBody.creative_id || '0'; - bid.cpm = responseCPM; - bid.requestId = bidRequest.bidId; - bid.width = 1; - bid.height = 1; - bid.ttl = 200; - bid.netRevenue = true; - bid.currency = 'USD'; - - if (serverBody.native) { - bid.native = serverBody.native; - } - if (serverBody.cobj) { - bid.cobj = serverBody.cobj; - } - if (!utils.isEmpty(bidRequest.sizes)) { - bid.width = bidRequest.sizes[0][0]; - bid.height = bidRequest.sizes[0][1]; - } - - bid.nurl = serverBody.nurl; - bid.sync = serverBody.sync; - if (bidRequest.renderMode && bidRequest.renderMode === 'banner') { - bid.mediaType = 'banner'; - if (serverBody.native) { - const adAssetsUrl = 'https://cdn.elasticad.net/native/serve/js/quantx/quantumAd/'; - let assets = serverBody.native.assets; - let link = serverBody.native.link; - - let trackers = []; - if (serverBody.native.imptrackers) { - trackers = serverBody.native.imptrackers; - } - - let jstracker = ''; - if (serverBody.native.jstracker) { - jstracker = encodeURIComponent(serverBody.native.jstracker); - } - - if (serverBody.nurl) { - trackers.push(serverBody.nurl); - } - - let ad = {}; - ad['trackers'] = trackers; - ad['jstrackers'] = jstracker; - ad['eventtrackers'] = serverBody.native.eventtrackers || []; - - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - switch (asset['id']) { - case 1: - ad['title'] = asset['title']['text']; - break; - case 2: - ad['sponsor_logo'] = asset['img']['url']; - break; - case 3: - ad['content'] = asset['data']['value']; - break; - case 4: - ad['main_image'] = asset['img']['url']; - break; - case 6: - ad['teaser_type'] = 'vast'; - ad['video_url'] = asset['video']['vasttag']; - break; - case 10: - ad['sponsor_name'] = asset['data']['value']; - break; - case 2001: - ad['expanded_content_type'] = 'embed'; - ad['expanded_summary'] = asset['data']['value']; - break; - case 2002: - ad['expanded_content_type'] = 'vast'; - ad['expanded_summary'] = asset['data']['value']; - break; - case 2003: - ad['sponsor_url'] = asset['data']['value']; - break; - case 2004: // prism - ad['content_type'] = 'prism'; - break; - case 2005: // internal_landing_page - ad['content_type'] = 'internal_landing_page'; - ad['internal_content_link'] = asset['data']['value']; - break; - case 2006: // teaser as vast - ad['teaser_type'] = 'vast'; - ad['video_url'] = asset['data']['value']; - break; - case 2007: - ad['autoexpand_content_type'] = asset['data']['value']; - break; - case 2022: // content page - ad['content_type'] = 'full_text'; - ad['full_text'] = asset['data']['value']; - break; - } - } - - ad['action_url'] = link.url; - - if (!ad['sponsor_url']) { - ad['sponsor_url'] = ad['action_url']; - } - - ad['clicktrackers'] = []; - if (link.clicktrackers) { - ad['clicktrackers'] = link.clicktrackers; - } - - ad['main_image'] = 'https://resize-ssp.adux.com/scalecrop-290x130/' + window.btoa(ad['main_image']) + '/external'; - - bid.ad = '
' + - '
' + - '
' + - ' ' + - ' ' + - '
' + - '

' + ad['title'] + '

' + - '

' + ad['content'] + ' 

' + - ' ' + - '
' + - '' + - '' + - '' + - '
'; - } - } else { - // native - bid.mediaType = 'native'; - if (bidRequest.mediaType === 'native') { - if (serverBody.native) { - let assets = serverBody.native.assets; - let link = serverBody.native.link; - - let trackers = []; - if (serverBody.native.imptrackers) { - trackers = serverBody.native.imptrackers; - } - - if (serverBody.nurl) { - trackers.push(serverBody.nurl); - } - - let native = {}; - - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - switch (asset['id']) { - case 1: - native.title = asset['title']['text']; - break; - case 2: - native.icon = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - break; - case 3: - native.body = asset['data']['value']; - break; - case 4: - native.image = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - break; - case 10: - native.sponsoredBy = asset['data']['value']; - break; - } - } - native.cta = 'read more'; - if (serverBody.language) { - native.cta = 'read more'; - } - - native.clickUrl = link.url; - native.impressionTrackers = trackers; - if (link.clicktrackers) { - native.clickTrackers = link.clicktrackers; - } - native.eventtrackers = native.eventtrackers || []; - - bid.qtx_native = utils.deepClone(serverBody.native); - bid.native = native; - } - } - } - bidResponses.push(bid); - } - - return bidResponses; - }, - - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse} serverResponse A successful response from the server - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function (syncOptions, serverResponse) { - const syncs = []; - utils._each(serverResponse, function(serverResponse) { - if (serverResponse.body && serverResponse.body.sync) { - utils._each(serverResponse.body.sync, function (pixel) { - syncs.push({ - type: 'image', - url: pixel - }); - }); - } - }); - return syncs; - } -} -registerBidder(spec); diff --git a/modules/quantumBidAdapter.md b/modules/quantumBidAdapter.md deleted file mode 100644 index 572ca9ecd37..00000000000 --- a/modules/quantumBidAdapter.md +++ /dev/null @@ -1,94 +0,0 @@ -# Overview - -``` -Module Name: Quantum Advertising Bid Adapter -Module Type: Bidder Adapter -Maintainer: support.mediareporting@adux.com -``` - -# Description - -Connects to Quantum's ssp for bids. - -# Sample Ad Unit: For Publishers -``` -var adUnits = [{ - code: 'quantum-adUnit-id-1', - sizes: [[300, 250]], - bids: [{ - bidder: 'quantum', - params: { - placementId: 21546 //quantum adUnit id - } - }] - },{ - code: 'quantum-native-adUnit-id-1', - sizes: [[0, 0]], - mediaTypes: 'native', - bids: [{ - bidder: 'quantum', - params: { - placementId: 21546 //quantum adUnit id - } - }] - }]; -``` - -# Ad Unit and Setup: For Testing - -``` - - - - - - - - - - ``` diff --git a/modules/quantumdexBidAdapter.md b/modules/quantumdexBidAdapter.md deleted file mode 100644 index 8c35ea8cb05..00000000000 --- a/modules/quantumdexBidAdapter.md +++ /dev/null @@ -1,56 +0,0 @@ -# Overview - -``` -Module Name: Quantum Digital Exchange Bidder Adapter -Module Type: Bidder Adapter -Maintainer: ken@quantumdex.io -``` - -# Description - -Connects to Quantum Digital Exchange for bids. -Quantumdex bid adapter supports Banner and Video (Instream and Outstream) ads. - -# Test Parameters -``` -var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [ - { - bidder: 'quantumdex', - params: { - siteId: 'quantumdex-site-id', // siteId provided by Quantumdex - } - } - ] - } -]; -``` - -# Video Test Parameters -``` -var videoAdUnit = { - code: 'test-div', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - }, - }, - bids: [ - { - bidder: 'quantumdex', - params: { - siteId: 'quantumdex-site-id', // siteId provided by Quantumdex - } - } - ] -}; -``` \ No newline at end of file diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js new file mode 100644 index 00000000000..c9a86f73910 --- /dev/null +++ b/modules/qwarryBidAdapter.js @@ -0,0 +1,102 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'qwarry'; +export const ENDPOINT = 'https://bidder.qwarry.co/bid/adtag?prebid=true' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.zoneToken); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let bids = []; + validBidRequests.forEach(bidRequest => { + bids.push({ + bidId: bidRequest.bidId, + zoneToken: bidRequest.params.zoneToken, + pos: bidRequest.params.pos, + sizes: prepareSizes(bidRequest.sizes) + }) + }) + + let payload = { + requestId: bidderRequest.bidderRequestId, + bids, + referer: bidderRequest.refererInfo.referer, + schain: validBidRequests[0].schain + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdprConsent = { + consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : false, + consentString: bidderRequest.gdprConsent.consentString + } + } + + const options = { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + + return { + method: 'POST', + url: ENDPOINT, + data: payload, + options + }; + }, + + interpretResponse: function (serverResponse, request) { + const serverBody = serverResponse.body; + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + const { prebidResponse } = serverBody; + if (!prebidResponse || typeof prebidResponse !== 'object') { + return []; + } + + let bids = []; + prebidResponse.forEach(bidResponse => { + let bid = deepClone(bidResponse); + bid.cpm = parseFloat(bidResponse.cpm); + + // banner or video + if (VIDEO === bid.format) { + bid.vastXml = bid.ad; + } + + bid.meta = {}; + bid.meta.advertiserDomains = bid.adomain || []; + + bids.push(bid); + }) + + return bids; + }, + + onBidWon: function (bid) { + if (bid.winUrl) { + const cpm = bid.cpm; + const winUrl = bid.winUrl.replace(/\$\{AUCTION_PRICE\}/, cpm); + ajax(winUrl, null); + return true; + } + return false; + } +} + +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + +registerBidder(spec); diff --git a/modules/qwarryBidAdapter.md b/modules/qwarryBidAdapter.md new file mode 100644 index 00000000000..056ccb51293 --- /dev/null +++ b/modules/qwarryBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Qwarry Bidder Adapter +Module Type: Bidder Adapter +Maintainer: akascheev@asteriosoft.com +``` + +# Description + +Connects to Qwarry Bidder for bids. +Qwarry bid adapter supports Banner and Video ads. + +# Test Parameters +``` +const adUnits = [ + { + bids: [ + { + bidder: 'qwarry', + params: { + zoneToken: '?????????????????????', // zoneToken provided by Qwarry + } + } + ] + } +]; +``` diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index 1f07cc07b91..5cc88440629 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.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; + } + + 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); + + 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) { @@ -97,9 +102,48 @@ export const spec = { bidResponses.push(bidResponse); } return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = [] + + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + 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; } } +function appendToUrl(url, what) { + if (!what) { + return url; + } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; +} + function objectToQueryString(obj, prefix) { let str = []; let p; @@ -114,21 +158,31 @@ 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) { +function prepareExtraParams(params, payload, bidderRequest, bidRequest) { if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } + + if (bidderRequest && bidderRequest.gdprConsent) { + if (payload.pfilter !== undefined) { + payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; + } else { + payload.pfilter = { + 'gdpr_consent': bidderRequest.gdprConsent.consentString, + 'gdpr': bidderRequest.gdprConsent.gdprApplies + }; + } + } + if (params.bcat !== undefined) { payload.bcat = params.bcat; } @@ -146,6 +200,77 @@ function prepareExtraParams(params, payload) { 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' || !!utils.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' || !!utils.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(utils.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(utils.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/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index c72bbdd658f..31e430d79f9 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -46,6 +46,19 @@ export const spec = { } }; + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString || '' + }, + }; + request.regs = { + ext: { + gdpr: bidderRequest.gdprConsent.gdprApplies !== undefined ? bidderRequest.gdprConsent.gdprApplies : true + } + }; + } + return { method: 'POST', url: ENDPOINT, @@ -87,6 +100,11 @@ function bidResponseAvailable(bidRequest, bidResponse) { currency: bidResponse.cur, native: nativeResponse(idToImpMap[id], idToBidMap[id]) }; + if (idToBidMap[id].adomain) { + bid.meta = { + advertiserDomains: idToBidMap[id].adomain + } + } bids.push(bid); } }); @@ -94,11 +112,21 @@ function bidResponseAvailable(bidRequest, bidResponse) { } function impression(slot) { + let bidFloorFromModule + if (typeof slot.getFloor === 'function') { + const floorInfo = slot.getFloor({ + currency: 'USD', + mediaType: 'native', + size: '\*' + }); + bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined; + } return { id: slot.bidId, native: nativeImpression(slot), - bidfloor: slot.params.bidfloor || 0, - bidfloorcur: slot.params.bidfloorcur || 'USD' + bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, + bidfloorcur: (bidFloorFromModule && 'USD') || slot.params.bidfloorcur || 'USD', + tagId: slot.params.tagId || '0' }; } diff --git a/modules/readpeakBidAdapter.md b/modules/readpeakBidAdapter.md index a15767f29a7..da250e7f77a 100644 --- a/modules/readpeakBidAdapter.md +++ b/modules/readpeakBidAdapter.md @@ -4,7 +4,7 @@ Module Name: ReadPeak Bid Adapter Module Type: Bidder Adapter -Maintainer: kurre.stahlberg@readpeak.com +Maintainer: devteam@readpeak.com # Description @@ -23,7 +23,8 @@ Please reach out to your account team or hello@readpeak.com for more information params: { bidfloor: 5.00, publisherId: 'test', - siteId: 'test' + siteId: 'test', + tagId: 'test-tag-1' }, }] }]; diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 95e62397c2c..5540d136dea 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -42,7 +42,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 +306,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" @@ -670,7 +688,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 diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js new file mode 100644 index 00000000000..f8636862d4c --- /dev/null +++ b/modules/reconciliationRtdProvider.js @@ -0,0 +1,295 @@ +/** + * This module adds reconciliation provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will add custom targetings to ad units + * The module will listen to post messages from rendered creatives with Reconciliation Tag + * The module will call tracking pixels to log info needed for reconciliation matching + * @module modules/reconciliationRtdProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} publisherMemberId + * @property {?string} initUrl + * @property {?string} impressionUrl + * @property {?boolean} allowAccess + */ + +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; + +/** @type {Object} */ +const MessageType = { + IMPRESSION_REQUEST: 'rsdk:impression:req', + IMPRESSION_RESPONSE: 'rsdk:impression:res', +}; +/** @type {ModuleParams} */ +const DEFAULT_PARAMS = { + initUrl: 'https://confirm.fiduciadlt.com/init', + impressionUrl: 'https://confirm.fiduciadlt.com/pimp', + allowAccess: false, +}; +/** @type {ModuleParams} */ +let _moduleParams = {}; + +/** + * Handle postMesssage from ad creative, track impression + * and send response to reconciliation ad tag + * @param {Event} e + */ +function handleAdMessage(e) { + let data = {}; + let adUnitId = ''; + let adDeliveryId = ''; + + try { + data = JSON.parse(e.data); + } catch (e) { + return; + } + + if (data.type === MessageType.IMPRESSION_REQUEST) { + if (utils.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); + if (adWin && adWin !== window.top) { + // 2. Find the GPT slot for the iframed window + const adSlot = getSlotByWin(adWin); + // 3. Get AdUnit IDs for the selected slot + if (adSlot) { + adUnitId = adSlot.getAdUnitPath(); + adDeliveryId = adSlot.getTargeting('RSDK_ADID'); + adDeliveryId = adDeliveryId.length + ? adDeliveryId[0] + : `${utils.timestamp()}-${utils.generateUUID()}`; + } + } + } + + // Call local impression callback + const args = Object.assign({}, data.args, { + publisherDomain: window.location.hostname, + publisherMemberId: _moduleParams.publisherMemberId, + adUnitId, + adDeliveryId, + }); + + track.trackPost(_moduleParams.impressionUrl, args); + + // Send response back to the Advertiser tag + let response = { + type: MessageType.IMPRESSION_RESPONSE, + id: data.id, + args: Object.assign( + { + publisherDomain: window.location.hostname, + }, + data.args + ), + }; + + // If access is allowed - add ad unit id to response + if (_moduleParams.allowAccess) { + Object.assign(response.args, { + adUnitId, + adDeliveryId, + }); + } + + e.source.postMessage(JSON.stringify(response), '*'); + } +} + +/** + * Get top iframe window for nested Window object + * - top + * -- iframe.window <-- top iframe window + * --- iframe.window + * ---- iframe.window <-- win + * + * @param {Window} win nested iframe window object + * @param {Window} topWin top window + */ +export function getTopIFrameWin(win, topWin) { + topWin = topWin || window; + + if (!win) { + return null; + } + + try { + while (win.parent !== topWin) { + win = win.parent; + } + return win; + } catch (e) { + return null; + } +} + +/** + * get all slots on page + * @return {Object[]} slot GoogleTag slots + */ +function getAllSlots() { + return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); +} + +/** + * get GPT slot by placement id + * @param {string} code placement id + * @return {?Object} + */ +function getSlotByCode(code) { + const slots = getAllSlots(); + if (!slots || !slots.length) { + return null; + } + return ( + find( + slots, + (s) => s.getSlotElementId() === code || s.getAdUnitPath() === code + ) || null + ); +} + +/** + * get GPT slot by iframe window + * @param {Window} win + * @return {?Object} + */ +export function getSlotByWin(win) { + const slots = getAllSlots(); + + if (!slots || !slots.length) { + return null; + } + + return ( + find(slots, (s) => { + let slotElement = document.getElementById(s.getSlotElementId()); + + if (slotElement) { + let slotIframe = slotElement.querySelector('iframe'); + + if (slotIframe && slotIframe.contentWindow === win) { + return true; + } + } + + return false; + }) || null + ); +} + +/** + * Init Reconciliation post messages listeners to handle + * impressions messages from ad creative + */ +function initListeners() { + window.addEventListener('message', handleAdMessage, false); +} + +/** + * Send init event to log + * @param {Array} adUnits + */ +function trackInit(adUnits) { + track.trackPost( + _moduleParams.initUrl, + { + adUnits, + publisherDomain: window.location.hostname, + publisherMemberId: _moduleParams.publisherMemberId, + } + ); +} + +/** + * Track event via POST request + * wrap method to allow stubbing in tests + * @param {string} url + * @param {Object} data + */ +export const track = { + trackPost(url, data) { + const ajax = ajaxBuilder(); + + ajax( + url, + function() {}, + JSON.stringify(data), + { + method: 'POST', + } + ); + } +} + +/** + * Set custom targetings for provided adUnits + * @param {string[]} adUnitsCodes + * @return {Object} key-value object with custom targetings + */ +function getReconciliationData(adUnitsCodes) { + const dataToReturn = {}; + const adUnitsToTrack = []; + + adUnitsCodes.forEach((adUnitCode) => { + if (!adUnitCode) { + return; + } + + const adSlot = getSlotByCode(adUnitCode); + const adUnitId = adSlot ? adSlot.getAdUnitPath() : adUnitCode; + const adDeliveryId = `${utils.timestamp()}-${utils.generateUUID()}`; + + dataToReturn[adUnitCode] = { + RSDK_AUID: adUnitId, + RSDK_ADID: adDeliveryId, + }; + + adUnitsToTrack.push({ + adUnitId, + adDeliveryId + }); + }, {}); + + // Track init event + trackInit(adUnitsToTrack); + + return dataToReturn; +} + +/** @type {RtdSubmodule} */ +export const reconciliationSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'reconciliation', + /** + * get data and send back to realTimeData module + * @function + * @param {string[]} adUnitsCodes + */ + getTargetingData: getReconciliationData, + init: init, +}; + +function init(moduleConfig) { + const params = moduleConfig.params; + if (params && params.publisherMemberId) { + _moduleParams = Object.assign({}, DEFAULT_PARAMS, params); + initListeners(); + } else { + utils.logError('missing params for Reconciliation provider'); + } + return true; +} + +submodule('realTimeData', reconciliationSubmodule); diff --git a/modules/reconciliationRtdProvider.md b/modules/reconciliationRtdProvider.md new file mode 100644 index 00000000000..53883ad99eb --- /dev/null +++ b/modules/reconciliationRtdProvider.md @@ -0,0 +1,49 @@ +The purpose of this Real Time Data Provider is to allow publishers to match impressions accross the supply chain. + +**Reconciliation SDK** +The purpose of Reconciliation SDK module is to collect supply chain structure information and vendor-specific impression IDs from suppliers participating in ad creative delivery and report it to the Reconciliation Service, allowing publishers, advertisers and other supply chain participants to match and reconcile ad server, SSP, DSP and veritifation system log file records. Reconciliation SDK was created as part of TAG DLT initiative ( https://www.tagtoday.net/pressreleases/dlt_9_7_2020 ). + +**Usage for Publishers:** + +Compile the Reconciliation Provider into your Prebid build: + +`gulp build --modules=reconciliationRtdProvider` + +Add Reconciliation real time data provider configuration by setting up a Prebid Config: + +```javascript +const reconciliationDataProvider = { + name: "reconciliation", + params: { + publisherMemberId: "test_prebid_publisher", // required + allowAccess: true, //optional + } +}; + +pbjs.setConfig({ + ..., + realTimeData: { + dataProviders: [ + reconciliationDataProvider + ] + } +}); +``` + +where: +- `publisherMemberId` (required) - ID associated with the publisher +- `access` (optional) true/false - Whether ad markup will recieve Ad Unit Id's via Reconciliation Tag + +**Example:** + +To view an example: + +- in your cli run: + +`gulp serve --modules=reconciliationRtdProvider,appnexusBidAdapter` + +Your could also change 'appnexusBidAdapter' to another one. + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/reconciliationRtdProvider_example.html` 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 b3b8a647137..14effa263a3 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -6,36 +6,28 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.5'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; const storage = getStorageManager(); function isBidRequestValid(bid) { - if (!utils.isSafariBrowser() && !hasUuid()) { - utils.logWarn('uuid is not found.'); - return false; - } if (!utils.deepAccess(bid, 'params.placementId')) { utils.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; + utils.logWarn('Invalid mediaType video.'); + } + if (hasBannerMediaType(bid) && isBannerValid(bid)) { + return true; + } else { + utils.logWarn('Invalid mediaType banner.'); } - return true; + return false; } function buildRequests(validBidRequests, bidderRequest) { @@ -47,7 +39,21 @@ function buildRequests(validBidRequests, bidderRequest) { const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; const uuid = getUuid(); - const mediaType = getMediaType(bidRequest); + let mediaType = ''; + let width = 0; + let height = 0; + + if (hasVideoMediaType(bidRequest) && isVideoValid(bidRequest)) { + const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + width = playerSize[0][0]; + height = playerSize[0][1]; + mediaType = VIDEO; + } else if (hasBannerMediaType(bidRequest) && isBannerValid(bidRequest)) { + const sizes = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')); + width = sizes[0][0]; + height = sizes[0][1]; + mediaType = BANNER; + } let payload = { version: ADAPTER_VERSION, @@ -61,18 +67,10 @@ function buildRequests(validBidRequests, bidderRequest) { transaction_id: bidRequest.transactionId, media_type: mediaType, uuid: uuid, + width: width, + height: height }; - if (hasVideoMediaType(bidRequest)) { - const playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); - payload.width = playerSize[0][0]; - payload.height = playerSize[0][1]; - } else if (hasBannerMediaType(bidRequest)) { - const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes'); - payload.width = sizes[0][0]; - payload.height = sizes[0][1]; - } - // It may not be encoded, so add it at the end of the payload payload.ref = bidderRequest.refererInfo.referer; @@ -87,10 +85,9 @@ function buildRequests(validBidRequests, bidderRequest) { player: bidRequest.params.player, width: payload.width, height: payload.height, - mediaType: mediaType, + mediaType: payload.media_type }); } - return bidRequests; } @@ -101,10 +98,6 @@ function interpretResponse(serverResponse, bidRequest) { return []; } - if (body.uuid) { - storage.setDataInLocalStorage(UUID_KEY, body.uuid); - } - const playerUrl = bidRequest.player || body.playerUrl; const mediaType = bidRequest.mediaType || VIDEO; @@ -119,6 +112,10 @@ function interpretResponse(serverResponse, bidRequest) { ttl: body.ttl || DEFAULT_TTL, netRevenue: true, mediaType: mediaType, + meta: { + advertiserDomains: body.adomain || [], + mediaType: VIDEO + } }; if (mediaType === VIDEO) { bidResponse.vastXml = body.vast; @@ -141,10 +138,9 @@ function getUserSyncs(syncOptions, serverResponses) { if (serverResponses.length > 0) { syncUrl = utils.deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; } - receiveMessage(); return [{ type: 'iframe', - url: syncUrl + url: `${syncUrl}?uu=${getUuid()}` }]; } @@ -171,6 +167,7 @@ function onTimeout(data) { auction_id: utils.deepAccess(data, '0.auctionId'), bid_id: utils.deepAccess(data, '0.bidId'), ad_unit_code: utils.deepAccess(data, '0.adUnitCode'), + version: ADAPTER_VERSION, ref: window.location.href, }).replace(/\&$/, ''); const bidDomain = utils.deepAccess(data, '0.params.0.domain') || BIDDER_DOMAIN; @@ -219,37 +216,20 @@ function outstreamRender(bid) { }); } -function receiveMessage() { - window.addEventListener('message', setUuid); -} - -function setUuid(e) { - if (utils.isPlainObject(e.data) && e.data.relaido_uuid) { - storage.setDataInLocalStorage(UUID_KEY, e.data.relaido_uuid); - window.removeEventListener('message', setUuid); - } -} - function isBannerValid(bid) { if (!isMobile()) { return false; } - const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); - if (sizes && utils.isArray(sizes)) { - if (utils.isArray(sizes[0])) { - const width = sizes[0][0]; - const height = sizes[0][1]; - if (width >= 300 && height >= 250) { - return true; - } - } + const sizes = getValidSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes')); + if (sizes.length > 0) { + return true; } return false; } function isVideoValid(bid) { - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (playerSize && utils.isArray(playerSize) && playerSize.length > 0) { + const playerSize = getValidSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize')); + if (playerSize.length > 0) { const context = utils.deepAccess(bid, 'mediaTypes.video.context'); if (context && context === 'outstream') { return true; @@ -258,12 +238,12 @@ function isVideoValid(bid) { return false; } -function hasUuid() { - return !!storage.getDataFromLocalStorage(UUID_KEY); -} - function getUuid() { - return storage.getDataFromLocalStorage(UUID_KEY) || ''; + const id = storage.getCookie(UUID_KEY) + if (id) return id; + const newId = utils.generateUUID(); + storage.setCookie(UUID_KEY, newId); + return newId; } export function isMobile() { @@ -274,15 +254,6 @@ 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'); } @@ -291,6 +262,25 @@ function hasVideoMediaType(bid) { return !!utils.deepAccess(bid, 'mediaTypes.video'); } +function getValidSizes(sizes) { + let result = []; + if (sizes && utils.isArray(sizes) && sizes.length > 0) { + for (let i = 0; i < sizes.length; i++) { + if (utils.isArray(sizes[i]) && sizes[i].length == 2) { + const width = sizes[i][0]; + const height = sizes[i][1]; + if (width == 1 && height == 1) { + return [[1, 1]]; + } + if ((width >= 300 && height >= 250)) { + result.push([width, height]); + } + } + } + } + return result; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], diff --git a/modules/relevantAnalyticsAdapter.js b/modules/relevantAnalyticsAdapter.js new file mode 100644 index 00000000000..5917262c810 --- /dev/null +++ b/modules/relevantAnalyticsAdapter.js @@ -0,0 +1,33 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const relevantAnalytics = adapter({ analyticsType: 'bundle', handler: 'on' }); + +const { enableAnalytics: orgEnableAnalytics } = relevantAnalytics; + +Object.assign(relevantAnalytics, { + /** + * Save event in the global array that will be consumed later by the Relevant Yield library + */ + track: ({ eventType: ev, args }) => { + window.relevantDigital.pbEventLog.push({ ev, args, ts: new Date() }); + }, + + /** + * Before forwarding the call to the original enableAnalytics function - + * create (if needed) the global array that is used to pass events to the Relevant Yield library + * by the 'track' function above. + */ + enableAnalytics: function(...args) { + window.relevantDigital = window.relevantDigital || {}; + window.relevantDigital.pbEventLog = window.relevantDigital.pbEventLog || []; + return orgEnableAnalytics.call(this, ...args); + }, +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: relevantAnalytics, + code: 'relevant', +}); + +export default relevantAnalytics; diff --git a/modules/relevantAnalyticsAdapter.md b/modules/relevantAnalyticsAdapter.md new file mode 100644 index 00000000000..e6383fa77e1 --- /dev/null +++ b/modules/relevantAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Relevant Yield Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [support@relevant-digital.com](mailto:support@relevant-digital.com) + +# Description + +Analytics adapter to be used with [Relevant Yield](https://www.relevant-digital.com/relevantyield) + +Contact [sales@relevant-digital.com](mailto:sales@relevant-digital.com) for information. 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 76% rename from modules/smartrtbBidAdapter.js rename to modules/resetdigitalBidAdapter.js index 12d5a0ae7da..7591c3391a0 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -2,30 +2,18 @@ import * as utils 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] - } -} +import {getOrigin} from '../src/utils.js'; +const BIDDER_CODE = 'resetdigital'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['smrtb'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null && - bid.params.medId !== 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) @@ -35,7 +23,7 @@ export const spec = { start_time: utils.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:'), @@ -57,18 +45,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: utils.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, @@ -102,13 +90,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) { @@ -120,11 +111,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..777060a5eb6 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -2,6 +2,7 @@ 'use strict'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'revcontent'; @@ -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) { + utils.triggerPixel(bid.nurl); + } return true; } }; @@ -273,3 +212,80 @@ function extractHostname(url) { return hostname; } + +function buildImp(bid, id) { + let bidfloor; + if (utils.isFn(bid.getFloor)) { + bidfloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }).floor; + } else { + bidfloor = utils.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 = utils.deepAccess(bid, `mediaTypes.banner`); + let nativeReq = utils.deepAccess(bid, `mediaTypes.native`); + if (bannerReq) { + let sizes = utils.getAdUnitSizes(bid); + imp.banner = { + w: sizes[0][0], + h: sizes[0][1], + format: sizes.map(wh => utils.parseGPTSingleSizeArrayToRtbSize(wh)), + } + } else if (nativeReq) { + 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); + 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 1acbcfd0463..36b2c369213 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -38,7 +38,7 @@ 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') { @@ -253,6 +253,9 @@ function RhythmOneBidAdapter() { cpm: parseFloat(bid.price), width: bid.w, height: bid.h, + meta: { + advertiserDomains: bid.adomain + }, creativeId: bid.crid, currency: 'USD', netRevenue: true, diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 43bef356a73..4b556e83236 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -9,28 +9,29 @@ let REFERER = ''; export const spec = { code: BIDDER_CODE, + gvlid: 108, aliases: ['ra'], supportedMediaTypes: [BANNER, VIDEO], /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {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. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ isBidRequestValid: function (bid) { return !!(bid.params && bid.params.pid && bid.params.supplyType); }, /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { - bidfloor: bid.params.bidfloor, + bidfloor: raiGetFloor(bid, config), ifa: bid.params.ifa, pid: bid.params.pid, supplyType: bid.params.supplyType, @@ -48,7 +49,10 @@ export const spec = { timeout: config.getConfig('bidderTimeout'), user: raiSetEids(bid), demand: raiGetDemandType(bid), - videoData: raiGetVideoInfo(bid) + 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) }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) @@ -73,11 +77,11 @@ export const spec = { }); }, /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; // try catch @@ -93,16 +97,23 @@ 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') { bidResponse.vastXml = response.vastXML; try { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { - bidResponse.renderer = Renderer.install({ - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } bidResponse.renderer.setRender(renderer); } } catch (e) { @@ -117,13 +128,13 @@ export const spec = { return bidResponses }, /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { const syncs = []; @@ -131,11 +142,15 @@ export const spec = { var syncUrl = ''; var consent = ''; + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { - consent = `consentString=’${gdprConsent.consentString}` + consent = `consentString=${gdprConsent.consentString}` } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand if (consent != '') { syncUrl += `&${consent}` @@ -146,7 +161,7 @@ export const spec = { }); } - if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0) { + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; if (consent != '') { syncUrl += `&${consent}` @@ -193,6 +208,10 @@ function raiGetVideoInfo(bid) { playerSize: bid.mediaTypes.video.playerSize, mimes: bid.mediaTypes.video.mimes }; + } else { + videoData = { + format: 'banner' + } } return videoData; } @@ -201,7 +220,7 @@ function raiSetEids(bid) { let eids = []; if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', utils.deepAccess(bid, `userId.id5id`)); + 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`)); @@ -241,3 +260,50 @@ function renderAd(bid) { window.raParams(raPlayerHB, raOutstreamHBPassback, true); } + +function raiGetResolution() { + let resolution = ''; + if (typeof window.screen != 'undefined') { + resolution = window.screen.width + 'x' + window.screen.height; + } + return resolution; +} + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + 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', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index 932cdb8f8de..f888117b166 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,6 +39,7 @@ Please reach out to your account manager for more information. "pid":"ADb1f40rmo", "supplyType":"site", "bidfloor":0.40, + "keywords": "key1=value1;key2=value2;key3=value3;" } }] } diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js new file mode 100644 index 00000000000..492653afb95 --- /dev/null +++ b/modules/riseBidAdapter.js @@ -0,0 +1,360 @@ +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 ADAPTER_VERSION = '5.0.0'; +const TTL = 360; +const CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const MODES = { + PRODUCTION: 'hb', + TEST: 'hb-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + gvlid: 1043, + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bidRequest) { + if (!bidRequest.params) { + utils.logWarn('no params have been set to Rise adapter'); + return false; + } + + if (!bidRequest.params.org) { + utils.logWarn('org is a mandatory param for Rise adapter'); + return false; + } + + return true; + }, + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + + const requests = []; + + bidRequests.forEach(bid => { + requests.push(buildVideoRequest(bid, bidderRequest)); + }); + + return requests; + }, + interpretResponse: function({body}) { + const bidResponses = []; + + const bidResponse = { + requestId: body.requestId, + cpm: body.cpm, + width: body.width, + height: body.height, + creativeId: body.requestId, + currency: body.currency, + netRevenue: body.netRevenue, + ttl: body.ttl || TTL, + vastXml: body.vastXml, + mediaType: VIDEO + }; + + if (body.adomain && body.adomain.length) { + bidResponse.meta = {}; + bidResponse.meta.advertiserDomains = body.adomain + } + bidResponses.push(bidResponse); + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.userSyncURL + }); + } + if (syncOptions.pixelEnabled && utils.isArray(response.body.userSyncPixels)) { + const pixels = response.body.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + } +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid) { + if (!utils.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} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function buildVideoRequest(bid, bidderRequest) { + const sellerParams = generateParameters(bid, bidderRequest); + const {params} = bid; + return { + method: 'GET', + url: getEndpoint(params.testMode), + data: sellerParams + }; +} + +/** + * Get the the ad size from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizes(bid) { + if (utils.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]; + } + return []; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (utils.isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && utils.contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : 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} + * @param bidderRequest {bidderRequest} + * @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 {bidderCode} = bidderRequest; + const domain = window.location.hostname; + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const requestParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: utils.timestamp(), + ad_unit_code: utils.getBidIdParameter('adUnitCode', bid), + tmax: timeout, + width: width, + height: height, + publisher_id: params.org, + 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: utils.getBidIdParameter('auctionId', bid), + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent) + }; + + const userIdsParam = utils.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 = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); + if (playbackMethod) { + requestParams.playback_method = playbackMethod; + } + const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); + if (placement) { + requestParams.placement = placement; + } + const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); + if (pos) { + requestParams.pos = pos; + } + const minduration = utils.deepAccess(bid, 'mediaTypes.video.minduration'); + if (minduration) { + requestParams.min_duration = minduration; + } + const maxduration = utils.deepAccess(bid, 'mediaTypes.video.maxduration'); + if (maxduration) { + requestParams.max_duration = maxduration; + } + const skip = utils.deepAccess(bid, 'mediaTypes.video.skip'); + if (skip) { + requestParams.skip = skip; + } + const linearity = utils.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) { + requestParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + requestParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (params.ifa) { + requestParams.ifa = params.ifa; + } + + if (bid.schain) { + requestParams.schain = getSupplyChain(bid.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + requestParams.page_url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + } + + return requestParams; +} diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md new file mode 100644 index 00000000000..6251b92e0a9 --- /dev/null +++ b/modules/riseBidAdapter.md @@ -0,0 +1,53 @@ +#Overview + +Module Name: Rise Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid-rise-engage@risecodes.com + + +# Description + +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. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `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 +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, + bids: [{ + bidder: 'rise', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + ifa: 'XXX-XXX', // Optional + placementId: '12345678', // Optional + testMode: false // Optional + } + }] + } + ]; +``` 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 3337c3f1b59..036bdfaebeb 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -7,6 +7,7 @@ const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; 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; // Codes defined by OpenRTB Native Ads 1.1 specification @@ -34,7 +35,7 @@ export const OPENRTB = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function (bid) { return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); @@ -55,6 +56,23 @@ export const spec = { request.regs = {ext: {gdpr: gdpr}}; request.user = {ext: {consent: consentStr}}; } + if (validBidRequests[0].schain) { + const schain = mapSchain(validBidRequests[0].schain); + if (schain) { + request.ext = { + schain: schain, + } + } + } + + if (validBidRequests[0].userIdAsEids) { + const eids = { eids: validBidRequests[0].userIdAsEids }; + if (request.user && request.user.ext) { + request.user.ext = { ...request.user.ext, ...eids }; + } else { + request.user = {ext: eids}; + } + } return { method: 'POST', @@ -85,6 +103,22 @@ export const spec = { }; registerBidder(spec); +/** + * @param {object} slot Ad Unit Params by Prebid + * @returns {int} floor by imp type + */ +function applyFloor(slot) { + const floors = []; + if (typeof slot.getFloor === 'function') { + Object.keys(slot.mediaTypes).forEach(type => { + if (includes(SUPPORTED_MEDIA_TYPES, type)) { + floors.push(slot.getFloor({ currency: DEFAULT_CURRENCY_ARR[0], mediaType: type, size: slot.sizes || '*' }).floor); + } + }); + } + return floors.length > 0 ? Math.max(...floors) : parseFloat(slot.params.bidfloor); +} + /** * @param {object} slot Ad Unit Params by Prebid * @returns {object} Imp by OpenRTB 2.5 §3.2.4 @@ -97,9 +131,9 @@ function mapImpression(slot) { tagid: slot.adUnitCode.toString() }; - const bidfloor = parseFloat(slot.params.bidfloor); + const bidfloor = applyFloor(slot); if (bidfloor) { - imp.bidfloor = bidfloor + imp.bidfloor = bidfloor; } return imp; @@ -151,12 +185,7 @@ function mapSource(slot) { const source = { tid: slot.transactionId, }; - const schain = mapSchain(slot.schain); - if (schain) { - source.ext = { - schain: schain - } - } + return source; } @@ -302,6 +331,9 @@ function interpretBannerBid(serverBid) { width: serverBid.w, height: serverBid.h, ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, netRevenue: true, currency: 'USD' } @@ -320,6 +352,9 @@ function interpretNativeBid(serverBid) { width: 1, height: 1, ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, netRevenue: true, currency: 'USD', native: interpretNativeAd(serverBid.adm), diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js new file mode 100644 index 00000000000..f5c648d5234 --- /dev/null +++ b/modules/rtbsapeBidAdapter.js @@ -0,0 +1,144 @@ +import * as utils 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'; +const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js'; +const MATCH_SRC = 'https://www.acint.net/mc/?dp=141'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['sape'], + 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 && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.video) && bid.params && bid.params.placeId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let tz = (new Date()).getTimezoneOffset() + let padInt = (v) => (v < 10 ? '0' + v : '' + v); + + return { + url: ENDPOINT, + method: 'POST', + data: { + auctionId: bidderRequest.auctionId, + requestId: bidderRequest.bidderRequestId, + bids: validBidRequests, + timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60), + refererInfo: bidderRequest.refererInfo + }, + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {{data: {bids: [{mediaTypes: {banner: boolean}}]}}} bidRequest Info describing the request to the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!(serverResponse.body && Array.isArray(serverResponse.body.bids))) { + return []; + } + + let bids = {}; + bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid); + + return serverResponse.body.bids + .filter(bid => typeof (bid.meta || {}).advertiserDomains !== 'undefined') + .map(bid => { + let requestBid = bids[bid.requestId]; + let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); + + 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; + } + + bid.playerMuted = muted; + bid.renderer = renderer + + renderer.setRender(setOutstreamRenderer); + } + + return bid; + }); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions) { + const sync = []; + if (syncOptions.iframeEnabled) { + sync.push({ + type: 'iframe', + url: MATCH_SRC + }); + } + return sync; + }, + + /** + * 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.nurl) { + triggerPixel(bid.nurl); + } + } +} + +/** + * Initialize RtbSape outstream player + * + * @param bid + */ +function setOutstreamRenderer(bid) { + let props = {}; + if (bid.vastUrl) { + props.url = bid.vastUrl; + } + if (bid.vastXml) { + props.xml = bid.vastXml; + } + bid.renderer.push(() => { + let player = window.sapeRtbPlayerHandler(bid.adUnitCode, bid.width, bid.height, bid.playerMuted, {singleton: true}); + props.onComplete = () => player.destroy(); + props.onError = () => player.destroy(); + player.addSlot(props); + }); +} + +registerBidder(spec); diff --git a/modules/rtbsapeBidAdapter.md b/modules/rtbsapeBidAdapter.md new file mode 100644 index 00000000000..6b1afe3867d --- /dev/null +++ b/modules/rtbsapeBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: RtbSape Bid Adapter +Module Type: Bidder Adapter +Maintainer: sergey@sape.ru +``` + +# Description +Our module makes it easy to integrate RtbSape demand sources into your website. + +Supported Ad format: +* Banner +* Video (instream and outstream) + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553307 + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [600, 340] + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553309 + } + }] + } +]; +``` 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 3aa7753d204..e235868f791 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -3,16 +3,49 @@ * @module modules/realTimeData */ +/** + * @interface UserConsentData + */ +/** + * @property + * @summary gdpr consent + * @name UserConsentData#gdpr + * @type {Object} + */ +/** + * @property + * @summary usp consent + * @name UserConsentData#usp + * @type {Object} + */ +/** + * @property + * @summary coppa + * @name UserConsentData#coppa + * @type {boolean} + */ + /** * @interface RtdSubmodule */ /** - * @function + * @function? * @summary return real time data - * @name RtdSubmodule#getData - * @param {AdUnit[]} adUnits - * @param {function} onDone + * @name RtdSubmodule#getTargetingData + * @param {string[]} adUnitsCodes + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +/** + * @function? + * @summary modify bid request data + * @name RtdSubmodule#getBidRequestData + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + * @param {Object} reqBidsConfigObj + * @param {function} callback */ /** @@ -23,14 +56,50 @@ */ /** - * @interface ModuleConfig + * @property + * @summary used to link submodule with config + * @name RtdSubmodule#config + * @type {Object} */ /** - * @property - * @summary sub module name - * @name ModuleConfig#name - * @type {string} + * @function + * @summary init sub module + * @name RtdSubmodule#init + * @param {SubmoduleConfig} config + * @param {UserConsentData} user consent + * @return {boolean} false to remove sub module + */ + +/** + * @function? + * @summary on auction init event + * @name RtdSubmodule#onAuctionInitEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +/** + * @function? + * @summary on auction end event + * @name RtdSubmodule#onAuctionEndEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +/** + * @function? + * @summary on bid response event + * @name RtdSubmodule#onBidResponseEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +/** + * @interface ModuleConfig */ /** @@ -40,41 +109,66 @@ * @type {number} */ +/** + * @property + * @summary list of sub modules + * @name ModuleConfig#dataProviders + * @type {SubmoduleConfig[]} + */ + +/** + * @interface SubModuleConfig + */ + /** * @property * @summary params for provide (sub module) - * @name ModuleConfig#params + * @name SubModuleConfig#params * @type {Object} */ /** * @property - * @summary timeout (if no auction dealy) - * @name ModuleConfig#timeout - * @type {number} + * @summary name + * @name ModuleConfig#name + * @type {string} + */ + +/** + * @property + * @summary delay auction for this sub module + * @name ModuleConfig#waitForIt + * @type {boolean} */ -import {getGlobal} from '../../src/prebidGlobal.js'; import {config} from '../../src/config.js'; -import {targeting} from '../../src/targeting.js'; -import {getHook, module} from '../../src/hook.js'; +import {module} from '../../src/hook.js'; import * as utils from '../../src/utils.js'; +import events from '../../src/events.js'; +import CONSTANTS from '../../src/constants.json'; +import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; -/** @type {number} */ -const DEF_TIMEOUT = 1000; /** @type {RtdSubmodule[]} */ -let subModules = []; +let registeredSubModules = []; +/** @type {RtdSubmodule[]} */ +export let subModules = []; /** @type {ModuleConfig} */ let _moduleConfig; +/** @type {SubmoduleConfig[]} */ +let _dataProviders = []; +/** @type {UserConsentData} */ +let _userConsent; /** * enable submodule in User ID * @param {RtdSubmodule} submodule */ export function attachRealTimeDataProvider(submodule) { - subModules.push(submodule); + registeredSubModules.push(submodule); } export function init(config) { @@ -85,91 +179,154 @@ export function init(config) { } confListener(); // unsubscribe config listener _moduleConfig = realTimeData; - if (typeof (_moduleConfig.auctionDelay) === 'undefined') { - _moduleConfig.auctionDelay = 0; - } - // delay bidding process only if auctionDelay > 0 - if (!_moduleConfig.auctionDelay > 0) { - getHook('bidsBackCallback').before(setTargetsAfterRequestBids); - } else { - getGlobal().requestBids.before(requestBidsHook); + _dataProviders = realTimeData.dataProviders; + setEventsListeners(); + getGlobal().requestBids.before(setBidRequestsData, 40); + initSubModules(); + }); +} + +function getConsentData() { + return { + gdpr: gdprDataHandler.getConsentData(), + usp: uspDataHandler.getConsentData(), + coppa: !!(config.getConfig('coppa')) + } +} + +/** + * call each sub module init function by config order + * if no init function / init return failure / module not configured - remove it from submodules list + */ +function initSubModules() { + _userConsent = getConsentData(); + let subModulesByOrder = []; + _dataProviders.forEach(provider => { + const sm = find(registeredSubModules, s => s.name === provider.name); + const initResponse = sm && sm.init && sm.init(provider, _userConsent); + if (initResponse) { + subModulesByOrder.push(Object.assign(sm, {config: provider})); } }); + 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) }) + }); } /** - * get data from sub module - * @param {AdUnit[]} adUnits received from auction - * @param {function} callback callback function on data received + * loop through configured data providers If the data provider has registered getBidRequestData, + * call it, providing reqBidsConfigObj, consent data and module params + * this allows submodules to modify bidders + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js */ -function getProviderData(adUnits, callback) { - const callbackExpected = subModules.length; - let dataReceived = []; - let processDone = false; - const dataWaitTimeout = setTimeout(() => { - processDone = true; - callback(dataReceived); - }, _moduleConfig.auctionDelay || _moduleConfig.timeout || DEF_TIMEOUT); +export function setBidRequestsData(fn, reqBidsConfigObj) { + _userConsent = getConsentData(); + const relevantSubModules = []; + const prioritySubModules = []; subModules.forEach(sm => { - sm.getData(adUnits, onDataReceived); + if (typeof sm.getBidRequestData !== 'function') { + return; + } + relevantSubModules.push(sm); + const config = sm.config; + if (config && config.waitForIt) { + prioritySubModules.push(sm); + } }); - function onDataReceived(data) { - if (processDone) { - return + const shouldDelayAuction = prioritySubModules.length && _moduleConfig.auctionDelay && _moduleConfig.auctionDelay > 0; + let callbacksExpected = prioritySubModules.length; + let isDone = false; + let waitTimeout; + + if (!relevantSubModules.length) { + return exitHook(); + } + + if (shouldDelayAuction) { + waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay); + } + + relevantSubModules.forEach(sm => { + sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) + }); + + if (!shouldDelayAuction) { + return exitHook(); + } + + function onGetBidRequestDataCallback() { + if (isDone) { + return; } - dataReceived.push(data); - if (dataReceived.length === callbackExpected) { - processDone = true; - clearTimeout(dataWaitTimeout); - callback(dataReceived); + if (this.config && this.config.waitForIt) { + callbacksExpected--; + } + if (callbacksExpected <= 0) { + return exitHook(); } } + + function exitHook() { + isDone = true; + clearTimeout(waitTimeout); + fn.call(this, reqBidsConfigObj); + } } /** - * delete invalid data received from provider - * this is to ensure working flow for GPT - * @param {Object} data received from provider - * @return {Object} valid data for GPT targeting + * loop through configured data providers If the data provider has registered getTargetingData, + * call it, providing ad unit codes, consent data and module params + * the sub mlodle will return data to set on the ad unit + * this function used to place key values on primary ad server per ad unit + * @param {Object} auction object received on auction end event */ -export function validateProviderDataForGPT(data) { - // data must be an object, contains object with string as value - if (typeof data !== 'object') { - return {}; +export function getAdUnitTargeting(auction) { + const relevantSubModules = subModules.filter(sm => typeof sm.getTargetingData === 'function'); + if (!relevantSubModules.length) { + return; } - for (let key in data) { - if (data.hasOwnProperty(key)) { - for (let innerKey in data[key]) { - if (data[key].hasOwnProperty(innerKey)) { - if (typeof data[key][innerKey] !== 'string') { - utils.logWarn(`removing ${key}: {${innerKey}:${data[key][innerKey]} } from GPT targeting because of invalid type (must be string)`); - delete data[key][innerKey]; - } - } - } + + // get data + const adUnitCodes = auction.adUnitCodes; + if (!adUnitCodes) { + return; + } + let targeting = []; + for (let i = relevantSubModules.length - 1; i >= 0; i--) { + const smTargeting = relevantSubModules[i].getTargetingData(adUnitCodes, relevantSubModules[i].config, _userConsent); + if (smTargeting && typeof smTargeting === 'object') { + targeting.push(smTargeting); + } else { + utils.logWarn('invalid getTargetingData response for sub module', relevantSubModules[i].name); } } - return data; -} - -/** - * run hook after bids request and before callback - * get data from provider and set key values to primary ad server - * @param {function} next - next hook function - * @param {AdUnit[]} adUnits received from auction - */ -export function setTargetsAfterRequestBids(next, adUnits) { - getProviderData(adUnits, (data) => { - if (data && Object.keys(data).length) { - const _mergedData = deepMerge(data); - if (Object.keys(_mergedData).length) { - setDataForPrimaryAdServer(_mergedData); - } + // place data on auction adUnits + const mergedTargeting = deepMerge(targeting); + auction.adUnits.forEach(adUnit => { + const kv = adUnit.code && mergedTargeting[adUnit.code]; + if (!kv) { + return } - next(adUnits); + adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] || {}, kv); }); + return auction.adUnits; } /** @@ -198,54 +355,5 @@ export function deepMerge(arr) { }, {}); } -/** - * run hook before bids request - * get data from provider and set key values to primary ad server & bidders - * @param {function} fn - hook function - * @param {Object} reqBidsConfigObj - request bids object - */ -export function requestBidsHook(fn, reqBidsConfigObj) { - getProviderData(reqBidsConfigObj.adUnits || getGlobal().adUnits, (data) => { - if (data && Object.keys(data).length) { - const _mergedData = deepMerge(data); - if (Object.keys(_mergedData).length) { - setDataForPrimaryAdServer(_mergedData); - addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, _mergedData); - } - } - return fn.call(this, reqBidsConfigObj); - }); -} - -/** - * set data to primary ad server - * @param {Object} data - key values to set - */ -function setDataForPrimaryAdServer(data) { - data = validateProviderDataForGPT(data); - if (utils.isGptPubadsDefined()) { - targeting.setTargetingForGPT(data, null) - } else { - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(() => { - targeting.setTargetingForGPT(data, null); - }); - } -} - -/** - * @param {AdUnit[]} adUnits - * @param {Object} data - key values to set - */ -function addIdDataToAdUnitBids(adUnits, data) { - adUnits.forEach(adUnit => { - adUnit.bids = adUnit.bids.map(bid => { - const rd = data[adUnit.code] || {}; - return Object.assign(bid, {realTimeData: rd}); - }) - }); -} - -init(config); module('realTimeData', attachRealTimeDataProvider); +init(config); diff --git a/modules/rtdModule/provider.md b/modules/rtdModule/provider.md index fb42e7188d3..116db160238 100644 --- a/modules/rtdModule/provider.md +++ b/modules/rtdModule/provider.md @@ -1,27 +1,33 @@ New provider must include the following: -1. sub module object: -``` -export const subModuleName = { - name: String, - getData: Function -}; -``` +1. sub module object with the following keys: -2. Function that returns the real time data according to the following structure: -``` +| param name | type | Scope | Description | Params | +| :------------ | :------------ | :------ | :------ | :------ | +| name | string | required | must match the name provided by the publisher in the on-page config | n/a | +| init | function | required | defines the function that does any auction-level initialization required | config, userConsent | +| getTargetingData | function | optional | defines a function that provides ad server targeting data to RTD-core | adUnitArray, config, userConsent | +| getBidRequestData | function | optional | defines a function that provides ad server targeting data to RTD-core | reqBidsConfigObj, callback, config, userConsent | +| onAuctionInitEvent | function | optional | listens to the AUCTION_INIT event and calls a sub-module function that lets it inspect and/or update the auction | auctionDetails, config, userConsent | +| onAuctionEndEvent | function |optional | listens to the AUCTION_END event and calls a sub-module function that lets it know when auction is done | auctionDetails, config, userConsent | +| onBidResponseEvent | function |optional | listens to the BID_RESPONSE event and calls a sub-module function that lets it know when a bid response has been collected | bidResponse, config, userConsent | + +2. `getTargetingData` function (if defined) should return ad unit targeting data according to the following structure: +```json { "adUnitCode":{ "key":"value", "key2":"value" }, "adUnitCode2":{ - "dataKey":"dataValue", + "dataKey":"dataValue" } } ``` 3. Hook to Real Time Data module: -``` +```javascript submodule('realTimeData', subModuleName); ``` + +4. See detailed documentation [here](https://docs.prebid.org/dev-docs/add-rtd-submodule.html) diff --git a/modules/rtdModule/realTimeData.md b/modules/rtdModule/realTimeData.md deleted file mode 100644 index b2859098b1f..00000000000 --- a/modules/rtdModule/realTimeData.md +++ /dev/null @@ -1,32 +0,0 @@ -## Real Time Data Configuration Example - -Example showing config using `browsi` sub module -``` - pbjs.setConfig({ - "realTimeData": { - "auctionDelay": 1000, - dataProviders[{ - "name": "browsi", - "params": { - "url": "testUrl.com", - "siteKey": "testKey", - "pubKey": "testPub", - "keyName":"bv" - } - }] - } - }); -``` - -Example showing real time data object received form `browsi` real time data provider -``` -{ - "adUnitCode":{ - "key":"value", - "key2":"value" - }, - "adUnitCode2":{ - "dataKey":"dataValue", - } -} -``` diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 00ad14dd316..3f140e32d12 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -5,7 +5,23 @@ 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'; + +const RUBICON_GVL_ID = 52; +export const storage = getStorageManager(RUBICON_GVL_ID, 'rubicon'); +const COOKIE_NAME = 'rpaSession'; +const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins +const END_EXPIRE_TIME = 21600000; // 6 hours + +const pbsErrorMap = { + 1: 'timeout-error', + 2: 'input-error', + 3: 'connect-error', + 4: 'request-error', + 999: 'generic-error' +} +let prebidGlobal = getGlobal(); const { EVENTS: { AUCTION_INIT, @@ -38,8 +54,24 @@ const cache = { auctions: {}, targeting: {}, timeouts: {}, + gpt: {}, }; +const BID_REJECTED_IPF = 'rejected-ipf'; + +export let rubiConf = { + pvid: utils.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) + } +}); + export function getHostNameFromReferer(referer) { try { rubiconAdapter.referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname; @@ -58,7 +90,7 @@ function stringProperties(obj) { } else if (typeof value !== 'string') { value = String(value); } - newObj[prop] = value; + newObj[prop] = value || undefined; return newObj; }, {}); } @@ -83,10 +115,11 @@ function formatSource(src) { return src.toLowerCase(); } -function sendMessage(auctionId, bidWonId) { +function sendMessage(auctionId, bidWonId, trigger) { function formatBid(bid) { return utils.pick(bid, [ 'bidder', + 'bidderDetail', 'bidId', bidId => utils.deepAccess(bid, 'bidResponse.pbsBidId') || utils.deepAccess(bid, 'bidResponse.seatBidId') || bidId, 'status', 'error', @@ -94,7 +127,7 @@ function sendMessage(auctionId, bidWonId) { if (source) { return source; } - return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.indexOf(bid.bidder) !== -1 + return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.some(s2sBidder => s2sBidder.toLowerCase() === bid.bidder) !== -1 ? 'server' : 'client' }, 'clientLatencyMillis', @@ -106,7 +139,9 @@ function sendMessage(auctionId, bidWonId) { 'dimensions', 'mediaType', 'floorValue', - 'floorRule' + 'floorRuleValue', + 'floorRule', + 'adomains' ]) : undefined ]); } @@ -117,7 +152,7 @@ function sendMessage(auctionId, bidWonId) { 'videoAdFormat', () => bid.videoAdFormat, 'mediaTypes' ]), { - adserverTargeting: stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), + adserverTargeting: !utils.isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, bidwonStatus: 'success', // hard-coded for now accountId, siteId: bid.siteId, @@ -126,17 +161,26 @@ function sendMessage(auctionId, bidWonId) { }); } let auctionCache = cache.auctions[auctionId]; - let referrer = config.getConfig('pageUrl') || auctionCache.referrer; + let referrer = config.getConfig('pageUrl') || (auctionCache && auctionCache.referrer); let message = { - eventTimeMillis: Date.now(), - integration: config.getConfig('rubicon.int_type') || DEFAULT_INTEGRATION, + timestamps: { + prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, + auctionEnded: auctionCache.endTs, + eventTime: Date.now() + }, + trigger, + integration: rubiConf.int_type || DEFAULT_INTEGRATION, version: '$prebid.version$', referrerUri: referrer, - referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer) + referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), + channel: 'web', }; - const wrapperName = config.getConfig('rubicon.wrapperName'); - if (wrapperName) { - message.wrapperName = wrapperName; + if (rubiConf.wrapperName) { + message.wrapper = { + name: rubiConf.wrapperName, + family: rubiConf.wrapperFamily, + rule: rubiConf.rule_name + } } if (auctionCache && !auctionCache.sent) { let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { @@ -148,8 +192,10 @@ function sendMessage(auctionId, bidWonId) { 'transactionId', 'mediaTypes', 'dimensions', - 'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), - 'adSlot' + 'adserverTargeting', () => !utils.isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, + 'gam', gam => !utils.isEmpty(gam) ? gam : undefined, + 'pbAdSlot', + 'pattern' ]); adUnit.bids = []; adUnit.status = 'no-bid'; // default it to be no bid @@ -190,26 +236,67 @@ function sendMessage(auctionId, bidWonId) { clientTimeoutMillis: auctionCache.timeout, samplingFactor, accountId, - adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]) + adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), + requestId: auctionId }; // pick our of top level floor data we want to send! if (auctionCache.floorData) { - auction.floors = utils.pick(auctionCache.floorData, [ - 'location', - 'modelName', () => auctionCache.floorData.modelVersion, - 'skipped', - 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), - 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), - 'skipRate', skipRate => !isNaN(skipRate) ? skipRate : 0, - 'fetchStatus' + if (auctionCache.floorData.location === 'noData') { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'fetchStatus', + 'floorProvider as provider' + ]); + } else { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'modelVersion as modelName', + 'modelWeight', + 'modelTimestamp', + 'skipped', + 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), + 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), + 'skipRate', + 'fetchStatus', + 'floorMin', + 'floorProvider as provider' + ]); + } + } + + // gather gdpr info + if (auctionCache.gdprConsent) { + auction.gdpr = utils.pick(auctionCache.gdprConsent, [ + 'gdprApplies as applies', + 'consentString', + 'apiVersion as version' ]); } + // gather session info + if (auctionCache.session) { + message.session = utils.pick(auctionCache.session, [ + 'id', + 'pvid', + 'start', + 'expires' + ]); + if (!utils.isEmpty(auctionCache.session.fpkvs)) { + message.fpkvs = Object.keys(auctionCache.session.fpkvs).map(key => { + return { key, value: auctionCache.session.fpkvs[key] }; + }); + } + } + if (serverConfig) { 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) => { @@ -241,6 +328,10 @@ function sendMessage(auctionId, bidWonId) { ); } +function adUnitIsOnlyInstream(adUnit) { + return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && utils.deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; +} + function getBidPrice(bid) { // get the cpm from bidResponse let cpm; @@ -263,13 +354,13 @@ function getBidPrice(bid) { } // otherwise we convert and return try { - return Number(getGlobal().convertCurrency(cpm, currency, 'USD')); + return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); } catch (err) { utils.logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); } } -export function parseBidResponse(bid, previousBidResponse) { +export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { // The current bidResponse for this matching requestId/bidRequestId let responsePrice = getBidPrice(bid) // we need to compare it with the previous one (if there was one) @@ -281,16 +372,55 @@ export function parseBidResponse(bid, previousBidResponse) { 'dealId', 'status', 'mediaType', - 'dimensions', () => utils.pick(bid, [ - 'width', - 'height' - ]), - 'seatBidId', + 'dimensions', () => { + const width = bid.width || bid.playerWidth; + const height = bid.height || bid.playerHeight; + 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'), - 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined + 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), + 'floorRule', () => utils.debugTurnedOn() ? utils.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 + } ]); } +/* + Filters and converts URL Params into an object and returns only KVs that match the 'utm_KEY' format +*/ +function getUtmParams() { + let search; + + try { + search = utils.parseQS(utils.getWindowLocation().search); + } catch (e) { + search = {}; + } + + return Object.keys(search).reduce((accum, param) => { + if (param.match(/utm_/)) { + accum[param.replace(/utm_/, '')] = search[param]; + } + return accum; + }, {}); +} + +function getFpkvs() { + rubiConf.fpkvs = Object.assign((rubiConf.fpkvs || {}), getUtmParams()); + + // convert all values to strings + Object.keys(rubiConf.fpkvs).forEach(key => { + rubiConf.fpkvs[key] = rubiConf.fpkvs[key] + ''; + }); + + return rubiConf.fpkvs; +} + let samplingFactor = 1; let accountId; // List of known rubicon aliases @@ -309,8 +439,90 @@ function setRubiconAliases(aliasRegistry) { }); } +function getRpaCookie() { + let encodedCookie = storage.getDataFromLocalStorage(COOKIE_NAME); + if (encodedCookie) { + try { + return JSON.parse(window.atob(encodedCookie)); + } catch (e) { + utils.logError(`Rubicon Analytics: Unable to decode ${COOKIE_NAME} value: `, e); + } + } + return {}; +} + +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); + } +} + +function updateRpaCookie() { + const currentTime = Date.now(); + let decodedRpaCookie = getRpaCookie(); + if ( + !Object.keys(decodedRpaCookie).length || + (currentTime - decodedRpaCookie.lastSeen) > LAST_SEEN_EXPIRE_TIME || + decodedRpaCookie.expires < currentTime + ) { + decodedRpaCookie = { + id: utils.generateUUID(), + start: currentTime, + expires: currentTime + END_EXPIRE_TIME, // six hours later, + } + } + // 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.pvid = rubiConf.pvid; + setRpaCookie(decodedRpaCookie) + } + return decodedRpaCookie; +} + +function subscribeToGamSlots() { + window.googletag.pubads().addEventListener('slotRenderEnded', event => { + const isMatchingAdSlot = utils.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 => { + let bid = cache.auctions[auctionId].bids[bidId]; + // if this slot matches this bids adUnit, add the adUnit info + if (isMatchingAdSlot(bid.adUnit.adUnitCode)) { + // mark this adUnit as having been rendered by gam + cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; + + bid.adUnit.gam = utils.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, + 'adSlot', () => event.slot.getAdUnitPath(), + 'isSlotEmpty', () => event.isEmpty || undefined + ]); + } + }); + // Now if all adUnits have gam rendered, send the payload + if (rubiConf.waitForGamSlots && !cache.auctions[auctionId].sent && Object.keys(cache.auctions[auctionId].gamHasRendered).every(adUnitCode => cache.auctions[auctionId].gamHasRendered[adUnitCode])) { + clearTimeout(cache.timeouts[auctionId]); + delete cache.timeouts[auctionId]; + if (rubiConf.analyticsEventDelay > 0) { + setTimeout(() => sendMessage.call(rubiconAdapter, auctionId, undefined, 'delayedGam'), rubiConf.analyticsEventDelay) + } else { + sendMessage.call(rubiconAdapter, auctionId, undefined, 'gam') + } + } + }); + }); +} + let baseAdapter = adapter({analyticsType: 'endpoint'}); let rubiconAdapter = Object.assign({}, baseAdapter, { + MODULE_INITIALIZED_TIME: Date.now(), referrerHostname: '', enableAnalytics(config = {}) { let error = false; @@ -353,7 +565,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { }, disableAnalytics() { this.getUrl = baseAdapter.getUrl; - accountId = null; + accountId = undefined; + rubiConf = {}; + cache.gpt.registered = false; baseAdapter.disableAnalytics.apply(this, arguments); }, track({eventType, args}) { @@ -367,22 +581,45 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { ]); cacheEntry.bids = {}; cacheEntry.bidsWon = {}; - cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer; - if (utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData')) { - cacheEntry.floorData = {...utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData')}; + cacheEntry.gamHasRendered = {}; + cacheEntry.referrer = utils.deepAccess(args, 'bidderRequests.0.refererInfo.referer'); + const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (floorData) { + cacheEntry.floorData = {...floorData}; } + cacheEntry.gdprConsent = utils.deepAccess(args, 'bidderRequests.0.gdprConsent'); + cacheEntry.session = storage.localStorageIsEnabled() && updateRpaCookie(); + cacheEntry.userIds = Object.keys(utils.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()) { + 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() { + subscribeToGamSlots(); + }); + } break; case BID_REQUESTED: 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 && !adUnitIsOnlyInstream(bid)) { + cache.auctions[args.auctionId].gamHasRendered[bid.adUnitCode] = false; + } + memo[bid.bidId] = utils.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 - 'finalSource as source', + 'source', () => formatSource(bid.src), 'params', (params, bid) => { switch (bid.bidder) { // specify bidder params we want here @@ -442,6 +679,13 @@ 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} + } + }, + 'pbAdSlot', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), + 'pattern', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.aupname') ]) ]); return memo; @@ -449,10 +693,17 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { break; case BID_RESPONSE: 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].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.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { - bid.adUnit.adSlot = args.floorData.matchedFields.gptSlot; + if (!utils.deepAccess(bid, 'adUnit.gam.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { + utils.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')) { @@ -469,7 +720,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { delete bid.error; // it's possible for this to be set by a previous timeout break; case NO_BID: - bid.status = args.status === BID_REJECTED ? 'rejected' : 'no-bid'; + bid.status = args.status === BID_REJECTED ? BID_REJECTED_IPF : 'no-bid'; delete bid.error; break; default: @@ -478,14 +729,26 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { code: 'request-error' }; } - bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp; + bid.clientLatencyMillis = bid.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp; bid.bidResponse = parseBidResponse(args, bid.bidResponse); break; case BIDDER_DONE: + const serverError = utils.deepAccess(args, 'serverErrors.0'); + const serverResponseTimeMs = args.serverResponseTimeMs; args.bids.forEach(bid => { let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; + } else if (serverResponseTimeMs && bid.source === 's2s') { + cachedBid.serverLatencyMillis = serverResponseTimeMs; + } + // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET + if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { + cachedBid.status = 'error'; + cachedBid.error = { + code: pbsErrorMap[serverError.code] || pbsErrorMap[999], + description: serverError.message + } } if (!cachedBid.status) { cachedBid.status = 'no-bid'; @@ -504,8 +767,8 @@ 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); - } else if (Object.keys(auctionCache.bidsWon).reduce((memo, adUnitCode) => { + 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]; return memo; @@ -513,23 +776,36 @@ 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); - }, 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 => { let auctionCache = cache.auctions[badBid.auctionId]; let bid = auctionCache.bids[badBid.bidId || badBid.requestId]; - bid.status = 'error'; - bid.error = { - code: 'timeout-error' - }; + // might be set already by bidder-done, so do not overwrite + if (bid.status !== 'error') { + 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 + }; + } }); break; } @@ -538,7 +814,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { adapterManager.registerAnalyticsAdapter({ adapter: rubiconAdapter, - code: 'rubicon' + code: 'rubicon', + gvlid: RUBICON_GVL_ID }); export default rubiconAdapter; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index cd621010a9b..d502d7efb25 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -2,27 +2,23 @@ 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 find from 'core-js-pure/features/array/find.js'; +import { Renderer } from '../src/Renderer.js'; +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'; +// renderer code at https://github.com/rubicon-project/apex2 -// always use https, regardless of whether or not current page is secure -export const FASTLANE_ENDPOINT = 'https://fastlane.rubiconproject.com/a/api/fastlane.json'; -export const VIDEO_ENDPOINT = 'https://prebid-server.rubiconproject.com/openrtb2/auction'; -export const SYNC_ENDPOINT = 'https://eus.rubiconproject.com/usync.html'; +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); +}); const GVLID = 52; -const DIGITRUST_PROP_NAMES = { - FASTLANE: { - id: 'dt.id', - keyv: 'dt.keyv', - pref: 'dt.pref' - }, - PREBID_SERVER: { - id: 'id', - keyv: 'keyv' - } -}; var sizeMap = { 1: '468x60', @@ -110,7 +106,21 @@ var sizeMap = { 274: '1800x200', 278: '320x500', 282: '320x400', - 288: '640x380' + 288: '640x380', + 548: '500x1000', + 550: '980x480', + 552: '300x200', + 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); @@ -162,9 +172,9 @@ export const spec = { source: { tid: bidRequest.transactionId }, - tmax: config.getConfig('TTL') || 1000, + tmax: bidderRequest.timeout, imp: [{ - exp: 300, + exp: config.getConfig('s2sConfig.defaultTtl'), id: bidRequest.adUnitCode, secure: 1, ext: { @@ -174,9 +184,13 @@ export const spec = { }], ext: { prebid: { + channel: { + name: 'pbjs', + version: $$PREBID_GLOBAL$$.version + }, cache: { vastxml: { - returnCreative: false // don't return the VAST + returnCreative: rubiConf.returnVast === true } }, targeting: { @@ -187,7 +201,7 @@ export const spec = { }, bidders: { rubicon: { - integration: config.getConfig('rubicon.int_type') || DEFAULT_PBS_INTEGRATION + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION } } } @@ -201,13 +215,23 @@ 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}}); + } + let bidFloor; - if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { - let floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'video', - size: parseSizes(bidRequest, 'video') - }); + if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { + let floorInfo; + try { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', + size: parseSizes(bidRequest, 'video') + }); + } catch (e) { + utils.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')); @@ -222,11 +246,6 @@ export const spec = { addVideoParameters(data, bidRequest); - const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); - if (digiTrust) { - utils.deepSetValue(data, 'user.ext.digitrust', digiTrust); - } - if (bidderRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module let gdprApplies; @@ -242,59 +261,15 @@ export const spec = { utils.deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (bidRequest.userId && typeof bidRequest.userId === 'object' && - (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { - utils.deepSetValue(data, 'user.ext.eids', []); - - if (bidRequest.userId.tdid) { - data.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: bidRequest.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - - if (bidRequest.userId.pubcid) { - data.user.ext.eids.push({ - source: 'pubcommon', - uids: [{ - id: bidRequest.userId.pubcid, - }] - }); - } - - // support liveintent ID - if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { - data.user.ext.eids.push({ - source: 'liveintent.com', - uids: [{ - id: bidRequest.userId.lipb.lipbid - }] - }); - - data.user.ext.tpid = { - source: 'liveintent.com', - uid: bidRequest.userId.lipb.lipbid - }; - - if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { - utils.deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); - } - } + const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + if (eids && eids.length) { + utils.deepSetValue(data, 'user.ext.eids', eids); + } - // support identityLink (aka LiveRamp) - if (bidRequest.userId.idl_env) { - data.user.ext.eids.push({ - source: 'liveramp.com', - uids: [{ - id: bidRequest.userId.idl_env - }] - }); - } + // set user.id value from config value + const configUserId = config.getConfig('user.id'); + if (configUserId) { + utils.deepSetValue(data, 'user.id', configUserId); } if (config.getConfig('coppa') === true) { @@ -305,35 +280,22 @@ export const spec = { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); - if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { - const bidderData = { - bidders: [ bidderRequest.bidderCode ], - config: { - fpd: {} - } - }; + const multibid = config.getConfig('multibid'); + if (multibid) { + utils.deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { + let obj = {}; - if (!utils.isEmpty(siteData)) { - bidderData.config.fpd.site = siteData; - } + Object.keys(i).forEach(key => { + obj[key.toLowerCase()] = i[key]; + }); - if (!utils.isEmpty(userData)) { - bidderData.config.fpd.user = userData; - } + result.push(obj); - utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); + return result; + }, [])); } - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); - } + applyFPD(bidRequest, VIDEO, data); // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { @@ -345,19 +307,19 @@ export const spec = { return { method: 'POST', - url: VIDEO_ENDPOINT, + url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, data, bidRequest } }); - if (config.getConfig('rubicon.singleRequest') !== true) { + if (rubiConf.singleRequest !== true) { // bids are not grouped if single request mode is not enabled requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', - url: FASTLANE_ENDPOINT, + 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; @@ -388,7 +350,7 @@ export const spec = { // SRA request returns grouped bidRequest arrays not a plain bidRequest aggregate.push({ method: 'GET', - url: FASTLANE_ENDPOINT, + 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; @@ -405,6 +367,7 @@ export const spec = { getOrderedParams: function(params) { const containsTgV = /^tg_v/ const containsTgI = /^tg_i/ + const containsUId = /^eid_|^tpid_/ const orderedParams = [ 'account_id', @@ -417,17 +380,15 @@ export const spec = { 'gdpr_consent', 'us_privacy', 'rp_schain', - 'tpid_tdid', - 'tpid_liveintent.com', - 'tg_v.LIseg', - 'dt.id', - 'dt.keyv', - 'dt.pref', - 'rf', - 'p_geo.latitude', - 'p_geo.longitude', - 'kw' - ].concat(Object.keys(params).filter(item => containsTgV.test(item))) + ].concat(Object.keys(params).filter(item => containsUId.test(item))) + .concat([ + 'x_liverampidl', + 'ppuid', + 'rf', + 'p_geo.latitude', + 'p_geo.longitude', + 'kw' + ]).concat(Object.keys(params).filter(item => containsTgV.test(item))) .concat(Object.keys(params).filter(item => containsTgI.test(item))) .concat([ 'tk_flint', @@ -495,17 +456,15 @@ export const spec = { const [latitude, longitude] = params.latLong || []; - const configIntType = config.getConfig('rubicon.int_type'); - const data = { 'account_id': params.accountId, 'site_id': params.siteId, 'zone_id': params.zoneId, 'size_id': parsedSizes[0], 'alt_size_ids': parsedSizes.slice(1).join(',') || undefined, - 'rp_floor': (params.floor = parseFloat(params.floor)) > 0.01 ? params.floor : 0.01, + 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', - 'tk_flint': `${configIntType || DEFAULT_INTEGRATION}_v$prebid.version$`, + 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), @@ -517,36 +476,65 @@ export const spec = { }; // If floors module is enabled and we get USD floor back, send it in rp_hard_floor else undfined - if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { - let floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'banner', - size: '*' - }); + if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { + let floorInfo; + try { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + } catch (e) { + utils.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 : ''; - - if (bidRequest.userId) { - if (bidRequest.userId.tdid) { - data['tpid_tdid'] = bidRequest.userId.tdid; - } - - // support liveintent ID - if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { - data['tpid_liveintent.com'] = bidRequest.userId.lipb.lipbid; - if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { - data['tg_v.LIseg'] = bidRequest.userId.lipb.segments.join(','); + let posMapping = {1: 'atf', 3: 'btf'}; + let pos = posMapping[utils.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'); + if (configUserId) { + data['ppuid'] = configUserId; + } + // loop through userIds and add to request + if (bidRequest.userIdAsEids) { + bidRequest.userIdAsEids.forEach(eid => { + try { + // special cases + if (eid.source === 'adserver.org') { + data['tpid_tdid'] = eid.uids[0].id; + data['eid_adserver.org'] = eid.uids[0].id; + } else if (eid.source === 'liveintent.com') { + data['tpid_liveintent.com'] = eid.uids[0].id; + data['eid_liveintent.com'] = eid.uids[0].id; + if (eid.ext && Array.isArray(eid.ext.segments) && eid.ext.segments.length) { + data['tg_v.LIseg'] = eid.ext.segments.join(','); + } + } else if (eid.source === 'liveramp.com') { + data['x_liverampidl'] = eid.uids[0].id; + } 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 { + // add anything else with this generic format + data[`eid_${eid.source}`] = `${eid.uids[0].id}^${eid.uids[0].atype || ''}`; + } + // send AE "ppuid" signal if exists, and hasn't already been sent + if (!data['ppuid']) { + // get the first eid.uids[*].ext.stype === 'ppuid', if one exists + const ppId = find(eid.uids, uid => uid.ext && uid.ext.stype === 'ppuid'); + if (ppId && ppId.id) { + data['ppuid'] = ppId.id; + } + } + } catch (e) { + utils.logWarn('Rubicon: error reading eid:', eid, e); } - } - - // support identityLink (aka LiveRamp) - if (bidRequest.userId.idl_env) { - data['tpid_liveramp.com'] = bidRequest.userId.idl_env; - } + }); } if (bidderRequest.gdprConsent) { @@ -561,44 +549,9 @@ export const spec = { data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); } - // visitor properties - const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user')); - Object.keys(visitorData).forEach((key) => { - if (visitorData[key] != null && key !== 'keywords') { - data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key]) - ? JSON.stringify(visitorData[key]) - : visitorData[key].toString(); // initialize array; - } - }); - - // inventory properties - const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context')); - Object.keys(inventoryData).forEach((key) => { - if (inventoryData[key] != null && key !== 'keywords') { - data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key]) - ? JSON.stringify(inventoryData[key]) - : inventoryData[key].toString(); - } - }); - - // keywords - const keywords = (params.keywords || []).concat( - utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [], - utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []); - data.kw = Array.isArray(keywords) && keywords.length ? keywords.join(',') : ''; - - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - data['tg_i.dfp_ad_unit_code'] = pbAdSlot.replace(/^\/+/, ''); - } + data['rp_maxbids'] = bidderRequest.bidLimit || 1; - // digitrust properties - const digitrustParams = _getDigiTrustQueryParams(bidRequest, 'FASTLANE'); - Object.assign(data, digitrustParams); + applyFPD(bidRequest, BANNER, data); if (config.getConfig('coppa') === true) { data['coppa'] = 1; @@ -666,7 +619,7 @@ export const spec = { cpm: bid.price || 0, bidderCode: seatbid.seat, ttl: 300, - netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true + 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'), }; @@ -679,6 +632,14 @@ export const spec = { bidObject.dealId = bid.dealid; } + if (bid.adomain) { + utils.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); + } + let serverResponseTimeMs = utils.deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; @@ -686,6 +647,7 @@ export const spec = { if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; + utils.deepSetValue(bidObject, 'meta.mediaType', VIDEO); const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' @@ -707,6 +669,11 @@ export const spec = { if (bid.adm) { bidObject.vastXml = bid.adm; } if (bid.nurl) { bidObject.vastUrl = bid.nurl; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } + + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + if (videoContext.toLowerCase() === 'outstream') { + bidObject.renderer = outstreamRenderer(bidObject); + } } else { utils.logWarn('Rubicon: video response received non-video media type'); } @@ -719,6 +686,8 @@ export const spec = { } let ads = responseObj.ads; + let lastImpId; + let multibid = 0; // video ads array is wrapped in an object if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { @@ -731,12 +700,14 @@ export const spec = { } return ads.reduce((bids, ad, i) => { + (ad.impression_id && lastImpId === ad.impression_id) ? multibid++ : lastImpId = ad.impression_id; + if (ad.status !== 'ok') { return bids; } // associate bidRequests; assuming ads matches bidRequest - const associatedBidRequest = Array.isArray(bidRequest) ? bidRequest[i] : bidRequest; + const associatedBidRequest = Array.isArray(bidRequest) ? bidRequest[i - multibid] : bidRequest; if (associatedBidRequest && typeof associatedBidRequest === 'object') { let bid = { @@ -746,12 +717,12 @@ export const spec = { cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes - netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true rubicon: { advertiserId: ad.advertiser, networkId: ad.network }, meta: { - advertiserId: ad.advertiser, networkId: ad.network + advertiserId: ad.advertiser, networkId: ad.network, mediaType: BANNER } }; @@ -759,6 +730,10 @@ export const spec = { bid.mediaType = ad.creative_type; } + if (ad.adomain) { + bid.meta.advertiserDomains = Array.isArray(ad.adomain) ? ad.adomain : [ad.adomain]; + } + if (ad.creative_type === VIDEO) { bid.width = associatedBidRequest.params.video.playerWidth; bid.height = associatedBidRequest.params.video.playerHeight; @@ -789,7 +764,7 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.iframeEnabled) { - // data is only assigned if params are available to pass to SYNC_ENDPOINT + // data is only assigned if params are available to pass to syncEndpoint let params = ''; if (gdprConsent && typeof gdprConsent.consentString === 'string') { @@ -808,7 +783,7 @@ export const spec = { hasSynced = true; return { type: 'iframe', - url: SYNC_ENDPOINT + params + url: `https://${rubiConf.syncHost || 'eus'}.rubiconproject.com/usync.html` + params }; } }, @@ -831,38 +806,6 @@ function _getScreenResolution() { return [window.screen.width, window.screen.height].join('x'); } -function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { - if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { - return null; - } - const propNames = DIGITRUST_PROP_NAMES[endpointName]; - - function getDigiTrustId() { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - - let digiTrustId = getDigiTrustId(); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } - - const digiTrustQueryParams = { - [propNames.id]: digiTrustId.id, - [propNames.keyv]: digiTrustId.keyv - }; - if (propNames.pref) { - digiTrustQueryParams[propNames.pref] = 0; - } - return digiTrustQueryParams; -} - /** * @param {BidRequest} bidRequest * @param bidderRequest @@ -890,6 +833,64 @@ function _renderCreative(script, impId) { `; } +function hideGoogleAdsDiv(adUnit) { + const el = adUnit.querySelector("div[id^='google_ads']"); + if (el) { + el.style.setProperty('display', 'none'); + } +} + +function hideSmartAdServerIframe(adUnit) { + const el = adUnit.querySelector("script[id^='sas_script']"); + const nextSibling = el && el.nextSibling; + if (nextSibling && nextSibling.localName === 'iframe') { + nextSibling.style.setProperty('display', 'none'); + } +} + +function renderBid(bid) { + // hide existing ad units + const adUnitElement = document.getElementById(bid.adUnitCode); + hideGoogleAdsDiv(adUnitElement); + hideSmartAdServerIframe(adUnitElement); + + // configure renderer + const config = bid.renderer.getConfig(); + bid.renderer.push(() => { + window.MagniteApex.renderAd({ + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + placement: { + attachTo: `#${bid.adUnitCode}`, + align: config.align || 'center', + position: config.position || 'append' + }, + closeButton: config.closeButton || false, + label: config.label || undefined, + collapse: config.collapse || true + }); + }); +} + +function outstreamRenderer(rtbBid) { + const renderer = Renderer.install({ + id: rtbBid.adId, + url: rubiConf.rendererUrl || DEFAULT_RENDERER_URL, + config: rubiConf.rendererConfig || {}, + loaded: false, + adUnitCode: rtbBid.adUnitCode + }); + + try { + renderer.setRender(renderBid); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + function parseSizes(bid, mediaType) { let params = bid.params; if (mediaType === 'video') { @@ -977,6 +978,80 @@ function addVideoParameters(data, bidRequest) { data.imp[0].video.h = size[1] } +function applyFPD(bidRequest, mediaType, data) { + const BID_FPD = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + 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; + + let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + 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 => { + let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { + result.push(obj.id); + return result; + }, []); + 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'); + } 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'); + }).toString() : prop.toString(); + } + }; + const addBannerData = function(obj, name, key, isParent = true) { + let val = validate(obj, key, name); + let loc = (MAP[key] && isParent) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; + 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) => { + if (name === 'site' && key === 'content' && fpd[name][key].data) { + addBannerData(fpd[name][key].data, name, 'data'); + } else if (key !== 'ext') { + addBannerData(fpd[name][key], name, key); + } else if (fpd[name][key].data) { + Object.keys(fpd[name].ext.data).forEach((key) => { + addBannerData(fpd[name].ext.data[key], name, key, false); + }); + } + }); + }); + Object.keys(impData).forEach((key) => { + (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + }); + } else { + if (Object.keys(impData).length) { + utils.mergeDeep(data.imp[0].ext, {data: impData}); + } + + utils.mergeDeep(data, fpd); + } +} + /** * @param sizes * @returns {*} @@ -1054,6 +1129,7 @@ function bidType(bid, log = false) { } } +export const resetRubiConf = () => rubiConf = {}; export function masSizeOrdering(sizes) { const MAS_SIZE_PRIORITY = [15, 2, 9]; @@ -1123,7 +1199,6 @@ export function hasValidVideoParams(bid) { var requiredParams = { mimes: arrayType, protocols: arrayType, - maxduration: numberType, linearity: numberType, api: arrayType } @@ -1148,7 +1223,7 @@ export function hasValidSupplyChainParams(schain) { if (!schain.nodes) return isValid; isValid = schain.nodes.reduce((status, node) => { if (!status) return status; - return requiredFields.every(field => node[field]); + return requiredFields.every(field => node.hasOwnProperty(field)); }, true); if (!isValid) utils.logError('Rubicon: required schain params missing'); return isValid; diff --git a/modules/rubiconBidAdapter.md b/modules/rubiconBidAdapter.md index beb29a6baf1..0ef22a81972 100644 --- a/modules/rubiconBidAdapter.md +++ b/modules/rubiconBidAdapter.md @@ -70,9 +70,9 @@ globalsupport@rubiconproject.com for more information. bids: [{ bidder: 'rubicon', params: { - accountId: '7780', - siteId: '87184', - zoneId: '412394', + accountId: 7780, + siteId: 87184, + zoneId: 412394, video: { language: 'en' } diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 98b3b86671c..1f2bb473174 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -1,4 +1,3 @@ -import { config } from '../src/config.js'; import { setS2STestingModule } from '../src/adapterManager.js'; let s2sTesting = {}; @@ -9,39 +8,31 @@ const CLIENT = 'client'; s2sTesting.SERVER = SERVER; s2sTesting.CLIENT = CLIENT; -var testing = false; // whether testing is turned on -var bidSource = {}; // store bidder sources determined from s2sConfing bidderControl +s2sTesting.bidSource = {}; // store bidder sources determined from s2sConfig bidderControl s2sTesting.globalRand = Math.random(); // if 10% of bidderA and 10% of bidderB should be server-side, make it the same 10% -// load s2sConfig -config.getConfig('s2sConfig', config => { - testing = config.s2sConfig && config.s2sConfig.testing; - s2sTesting.calculateBidSources(config.s2sConfig); -}); - -s2sTesting.getSourceBidderMap = function(adUnits = []) { +s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { var sourceBidders = {[SERVER]: {}, [CLIENT]: {}}; - // bail if testing is not turned on - if (!testing) { - return {[SERVER]: [], [CLIENT]: []}; - } - adUnits.forEach((adUnit) => { // if any adUnit bidders specify a bidSource, include them (adUnit.bids || []).forEach((bid) => { + // When a s2sConfig does not have testing=true and did not calc bid sources + if (allS2SBidders.indexOf(bid.bidder) > -1 && !s2sTesting.bidSource[bid.bidder]) { + s2sTesting.bidSource[bid.bidder] = SERVER; + } // calculate the source once and store on bid object bid.calcSource = bid.calcSource || s2sTesting.getSource(bid.bidSource); // if no bidSource at bid level, default to bidSource from bidder - bid.finalSource = bid.calcSource || bidSource[bid.bidder] || CLIENT; // default to client + bid.finalSource = bid.calcSource || s2sTesting.bidSource[bid.bidder] || CLIENT; // default to client // add bidder to sourceBidders data structure sourceBidders[bid.finalSource][bid.bidder] = true; }); }); // make sure all bidders in bidSource are in sourceBidders - Object.keys(bidSource).forEach((bidder) => { - sourceBidders[bidSource[bidder]][bidder] = true; + Object.keys(s2sTesting.bidSource).forEach((bidder) => { + sourceBidders[s2sTesting.bidSource[bidder]][bidder] = true; }); // return map of source => array of bidders @@ -53,18 +44,14 @@ s2sTesting.getSourceBidderMap = function(adUnits = []) { /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level - * @param s2sConfig server-to-server configuration + * @param s2sConfigs server-to-server configuration */ s2sTesting.calculateBidSources = function(s2sConfig = {}) { - // bail if testing is not turned on - if (!testing) { - return; - } - bidSource = {}; // reset bid sources // calculate bid source (server/client) for each s2s bidder + var bidderControl = s2sConfig.bidderControl || {}; (s2sConfig.bidders || []).forEach((bidder) => { - bidSource[bidder] = s2sTesting.getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server + s2sTesting.bidSource[bidder] = s2sTesting.getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server }); }; diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md new file mode 100755 index 00000000000..2d391da7628 --- /dev/null +++ b/modules/saambaaBidAdapter.md @@ -0,0 +1,69 @@ +# 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 + } + } + } + ] + }; +``` \ No newline at end of file diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js deleted file mode 100755 index b6acd7214a2..00000000000 --- a/modules/seedingAllianceBidAdapter.js +++ /dev/null @@ -1,219 +0,0 @@ -// jshint esversion: 6, es3: false, node: true -'use strict'; - -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'seedingAlliance'; -const DEFAULT_CUR = 'EUR'; -const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=nativendo'; - -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; - -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } -}; - -export const spec = { - code: BIDDER_CODE, - - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { - return !!bid.params.adUnitId; - }, - - buildRequests: (validBidRequests, bidderRequest) => { - const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = validBidRequests[0].transactionId; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; - let pubcid = null; - let url = bidderRequest.refererInfo.referer; - - const imp = validBidRequests.map((bid, id) => { - 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 w, h; - - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; - - return asset; - } - }) - .filter(Boolean); - - if (bid.params.url) { - url = bid.params.url; - } - - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; - }); - - if (validBidRequests[0].crumbs && validBidRequests[0].crumbs.pubcid) { - pubcid = validBidRequests[0].crumbs.pubcid; - } - - const request = { - id: bidderRequest.auctionId, - site: { - page: url - }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: { - buyeruid: pubcid - } - }; - - return { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(request), - bids: validBidRequests - }; - }, - - interpretResponse: function(serverResponse, { bids }) { - if (utils.isEmpty(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) { - return { - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: bid.netRevenue === 'net', - currency: cur, - mediaType: NATIVE, - bidderCode: BIDDER_CODE, - native: parseNative(bidResponse) - }; - } - }) - .filter(Boolean); - } -}; - -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); - }); - - const result = { - url: link.url, - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || 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 }; - } - }); - - 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); -} diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 018339fabe4..8b7629df211 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -6,20 +6,47 @@ const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; const SEEDTAG_SSP_ONTIMEOUT_ENDPOINT = 'https://s.seedtag.com/se/hb/timeout'; - +const ALLOWED_PLACEMENTS = { + inImage: true, + inScreen: true, + inArticle: true, + banner: true, + video: true +} const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' }; +const deviceConnection = { + FIXED: 'fixed', + MOBILE: 'mobile', + UNKNOWN: 'unknown' +}; + +const getConnectionType = () => { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {} + switch (connection.type || connection.effectiveType) { + case 'wifi': + case 'ethernet': + return deviceConnection.FIXED + case 'cellular': + case 'wimax': + return deviceConnection.MOBILE + default: + const isMobile = /iPad|iPhone|iPod/.test(navigator.userAgent) || /android/i.test(navigator.userAgent) + return isMobile ? deviceConnection.UNKNOWN : deviceConnection.FIXED + } +}; + function mapMediaType(seedtagMediaType) { if (seedtagMediaType === 'display') return BANNER; if (seedtagMediaType === 'video') return VIDEO; else return seedtagMediaType; } -function getMediaTypeFromBid(bid) { - return bid.mediaTypes && Object.keys(bid.mediaTypes)[0] +function hasVideoMediaType(bid) { + return (!!bid.mediaTypes && !!bid.mediaTypes.video) || (!!bid.params && !!bid.params.video) } function hasMandatoryParams(params) { @@ -27,57 +54,69 @@ function hasMandatoryParams(params) { !!params.publisherId && !!params.adUnitId && !!params.placement && - (params.placement === 'inImage' || - params.placement === 'inScreen' || - params.placement === 'banner' || - params.placement === 'video') + !!ALLOWED_PLACEMENTS[params.placement] ); } -function hasVideoMandatoryParams(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 && + utils.isArray(videoParams.playerSize) && + videoParams.playerSize.length > 0; +} - if (params.adPosition) { - bidRequest.adPosition = params.adPosition; +function buildBidRequest(validBidRequest) { + const params = validBidRequest.params; + const mediaTypes = utils._map( + Object.keys(validBidRequest.mediaTypes), + function (pbjsType) { + return mediaTypesMap[pbjsType]; } + ); - if (params.video) { - 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, @@ -88,8 +127,13 @@ function buildBid(seedtagBid) { currency: seedtagBid.currency, netRevenue: true, mediaType: mediaType, - ttl: seedtagBid.ttl + ttl: seedtagBid.ttl, + nurl: seedtagBid.nurl, + meta: { + advertiserDomains: seedtagBid && seedtagBid.adomain && seedtagBid.adomain.length > 0 ? seedtagBid.adomain : [] + } }; + if (mediaType === VIDEO) { bid.vastXml = seedtagBid.content; } else { @@ -116,7 +160,6 @@ export const spec = { code: BIDDER_CODE, aliases: [SEEDTAG_ALIAS], supportedMediaTypes: [BANNER, VIDEO], - /** * Determines whether or not the given bid request is valid. * @@ -124,8 +167,8 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return getMediaTypeFromBid(bid) === VIDEO - ? hasMandatoryParams(bid.params) && hasVideoMandatoryParams(bid.mediaTypes) + return hasVideoMediaType(bid) + ? hasMandatoryParams(bid.params) && hasMandatoryVideoParams(bid) : hasMandatoryParams(bid.params); }, @@ -142,7 +185,8 @@ export const spec = { cmp: !!bidderRequest.gdprConsent, timeout: bidderRequest.timeout, version: '$prebid.version$', - bidRequests: buildBidRequests(validBidRequests) + connectionType: getConnectionType(), + bidRequests: utils._map(validBidRequests, buildBidRequest) }; if (payload.cmp) { @@ -169,7 +213,7 @@ export const spec = { const serverBody = serverResponse.body; if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { return utils._map(serverBody.bids, function(bid) { - return buildBid(bid); + return buildBidResponse(bid); }); } else { return []; @@ -198,8 +242,18 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout(data) { - getTimeoutUrl(data); - utils.triggerPixel(SEEDTAG_SSP_ONTIMEOUT_ENDPOINT); + const url = getTimeoutUrl(data); + utils.triggerPixel(url); + }, + + /** + * Function to call when the adapter wins the auction + * @param {bid} Bid information received from the server + */ + onBidWon: function (bid) { + if (bid && bid.nurl) { + utils.triggerPixel(bid.nurl); + } } } registerBidder(spec); 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/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md deleted file mode 100644 index 87b51e665e2..00000000000 --- a/modules/serverbidBidAdapter.md +++ /dev/null @@ -1,44 +0,0 @@ -# Overview - -Module Name: Serverbid Bid Adapter - -Module Type: Bid Adapter - -Maintainer: jgrimes@serverbid.com, jswart@serverbid.com - -# Description - -Connects to Serverbid for receiving bids from configured demand sources. - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test-ad-1', - sizes: [[300, 250]], - bids: [ - { - bidder: 'serverbid', - params: { - networkId: '9969', - siteId: '980639' - } - } - ] - }, - { - code: 'test-ad-2', - sizes: [[300, 250]], - bids: [ - { - bidder: 'serverbid', - params: { - networkId: '9969', - siteId: '980639', - zoneIds: [178503] - } - } - ] - } - ]; -``` diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 5c2a3df0595..2e3abd6b1a2 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -1,333 +1,177 @@ /** - * 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 * as utils from '../src/utils.js'; import {submodule} from '../src/hook.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(); -/** - * 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; -} +const GVLID = 887; +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'); +function queuePixelCallback(pixelUrl, id = '', callback) { + if (!pixelUrl) { + return; } - if (Number.isInteger(now) === false) { - throw createError('time must be an integer'); - } + // Use pubcid as a cache buster + const urlInfo = utils.parseUrl(pixelUrl); + urlInfo.search.id = encodeURIComponent('pubcid:' + id); + const targetUrl = utils.buildUrl(urlInfo); - 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; + return function () { + utils.triggerPixel(targetUrl); + }; } -/** - * 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; +function hasOptedOut() { + return !!((storage.cookiesAreEnabled() && readValue(OPTOUT_NAME, COOKIE)) || + (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE))); } -/** - * 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(); -} - -/** @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 + * @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()) { + utils.logInfo('PubCommonId decode: Has opted-out'); + return undefined; + } + utils.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 {SubmoduleParams} [configParams] - * @returns {sharedId} + * @param {SubmoduleConfig} [config] Config object with params and storage properties + * @param {Object} consentData + * @param {string} storedId Existing pubcommon id + * @returns {IdResponse} */ - getId(configParams) { - const resp = function (callback) { - utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); - ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); - }; - return {callback: resp}; - }, + getId: function (config = {}, consentData, storedId) { + if (hasOptedOut()) { + utils.logInfo('PubCommonId: Has opted-out'); + return; + } + const coppa = coppaDataHandler.getCoppa(); + + if (coppa) { + utils.logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + return; + } + 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 && utils.hasDeviceAccess()) ? utils.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 configParams - * @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(configParams, storedId) { - 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(ID_SVC, existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); + extendId: function(config = {}, consentData, storedId) { + if (hasOptedOut()) { + utils.logInfo('PubCommonId: Has opted-out'); + return {id: undefined}; + } + const coppa = coppaDataHandler.getCoppa(); + if (coppa) { + utils.logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + return; + } + const {params: {extend = false, pixelUrl} = {}} = config; + + if (extend) { + if (pixelUrl) { + const callback = queuePixelCallback(pixelUrl, storedId); + return {callback: callback}; + } else { + return {id: storedId}; } - }; - 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 acb076ed97f..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/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 0d183be05df..24ab2530014 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,105 +1,190 @@ 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.2.1'; +const VERSION = '4.0.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 - }; - - 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; + const timeout = config.getConfig('bidderTimeout'); + + const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; + const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); + + const req = { + id: utils.generateUUID(), + at: 1, + cur: ['USD'], + tmax: timeout, + site: { + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, + }, + user: { + ext: { + eids: userIdAsEids(bidRequests[0]), + }, + }, + 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, + }; + + 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 (bidderRequest && bidderRequest.gdprConsent) { - query.consent_required = !!bidderRequest.gdprConsent.gdprApplies; - } + if (bidderRequest.uspConsent) { + req.regs.ext.us_privacy = bidderRequest.uspConsent; + } - if (bidderRequest && bidderRequest.uspConsent) { - query.us_privacy = bidderRequest.uspConsent - } + const imps = bidRequests.map(bidReq => { + const impression = {}; - if (bidRequest.userId && bidRequest.userId.tdid) { - query.ttduid = bidRequest.userId.tdid; + const gpid = utils.deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + impression.ext = { gpid: gpid }; } - if (bidRequest.schain) { - query.schain = JSON.stringify(bidRequest.schain); + // if request is for video, we only support instream + if (bidReq.mediaTypes && bidReq.mediaTypes.video && bidReq.mediaTypes.video.context === 'outstream') { + // return null so we can easily remove this imp from the array of imps that we send to adserver + return null; } - if (bidRequest.bidfloor) { - query.bidfloor = parseFloat(bidRequest.bidfloor); + if (bidReq.mediaTypes && bidReq.mediaTypes.video) { + const videoRequest = bidReq.mediaTypes.video; + + // 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: utils.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), + }; + + if (videoRequest.placement) impression.video.placement = videoRequest.placement; + 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: utils.deepAccess(bidReq, 'mediaTypes.banner.pos', 0), + topframe: utils.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 { - method: 'GET', - url: STR_ENDPOINT, - data: query, - strData: strData + id: bidReq.bidId, + tagid: String(bidReq.params.pkey), + secure: secure ? 1 : 0, + bidfloor: getBidRequestFloor(bidReq), + ...impression, }; - }) + }).filter(imp => !!imp); + + return { + method: 'POST', + url: STR_ENDPOINT, + data: { + ...req, + imp: imps, + }, + bidRequests, + bidderRequest, + }; }, 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 request = matchRequest(bid.impid, req); + + const response = { + requestId: bid.impid, + width: +bid.w, + height: +bid.h, + cpm: +bid.price, + creativeId: bid.crid, + dealId: bid.dealid || null, + mediaType: request.mediaTypes && request.mediaTypes.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) => { @@ -120,134 +205,77 @@ 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 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); +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; +} - 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'; +function userIdAsEids(bidRequest) { + const eids = createEidsArray(utils.deepAccess(bidRequest, 'userId')) || []; - // 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); - } + const flocData = utils.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 } }], + }); } -} -// 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); - } + return eids; } -// 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); - })); +function getProtocol() { + return window.location.protocol; } -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 matchRequest(id, request) { + return request.bidRequests.find(bid => bid.bidId === id); } -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 2290e370cae..218ef353d4e 100644 --- a/modules/sharethroughBidAdapter.md +++ b/modules/sharethroughBidAdapter.md @@ -23,16 +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 - Blocked Advertiser Domains + badv: ['domain1.com', 'domain2.com'], - // 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 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.md b/modules/shinezBidAdapter.md new file mode 100644 index 00000000000..e040cfbf36b --- /dev/null +++ b/modules/shinezBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Shinez Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech-team@shinez.io +``` + +# Description + +Connects to shinez.io demand sources. + +The Shinez adapter requires setup and approval from the Shinez team. +Please reach out to tech-team@shinez.io for more information. + +# Test Parameters + +```javascript +var adUnits = [{ + code: "test-div", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: "shinez", + params: { + placementId: "00654321" + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index d0eb8c6a589..8d94a8e508b 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -166,6 +166,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 = { diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js new file mode 100644 index 00000000000..373468b2f14 --- /dev/null +++ b/modules/sirdataRtdProvider.js @@ -0,0 +1,402 @@ +/** + * This module adds Sirdata provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server + * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) + * @module modules/sirdataRtdProvider + * @requires module:modules/realTimeData + */ +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils 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'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { config } from '../src/config.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'SirdataRTDModule'; + +export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + moduleConfig.params = moduleConfig.params || {}; + + var tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); + var gdprApplies = (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies ? userConsent.gdpr.gdprApplies : ''); + + moduleConfig.params.partnerId = moduleConfig.params.partnerId ? moduleConfig.params.partnerId : 1; + moduleConfig.params.key = moduleConfig.params.key ? moduleConfig.params.key : 1; + + var sirdataDomain; + var sendWithCredentials; + + if (userConsent.coppa || (userConsent.usp && (userConsent.usp[0] == '1' && (userConsent.usp[1] == 'N' || userConsent.usp[2] == 'Y')))) { + // if children or "Do not Sell" management in California, no segments, page categories only whatever TCF signal + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + gdprApplies = null; + tcString = ''; + } else if (getGlobal().getConfig('consentManagement.gdpr')) { + // Default endpoint is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given + sirdataDomain = 'cookieless-data.com'; + sendWithCredentials = false; + } + + // 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])) { + sirdataDomain = 'sddan.com'; + sendWithCredentials = true; + } + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer; + + const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + actualUrl : ''); + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.segments) { + addSegmentData(adUnits, data, moduleConfig, onDone); + } else { + onDone(); + } + } catch (e) { + onDone(); + utils.logError('unable to parse Sirdata data' + e); + } + } else if (req.status === 204) { + onDone(); + } + }, + error: function () { + onDone(); + utils.logError('unable to get Sirdata data'); + } + }, + null, + { + contentType: 'text/plain', + method: 'GET', + withCredentials: sendWithCredentials, + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); +} + +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 (!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 (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testGlobal, addOrtb2)}; + getGlobal().setConfig(ortb2); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +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 || {}); + } + 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 (!utils.isEmpty(addOrtb2)) { + let ortb2 = {ortb2: utils.mergeDeep({}, testBidder, addOrtb2)}; + getGlobal().setBidderConfig({ bidders: [bidder], config: ortb2 }); + } + } catch (e) { + utils.logError(e) + } + + return true; +} + +export function loadCustomFunction (todo, adUnit, list, data, bid) { + try { + if (typeof todo == 'function') { + todo(adUnit, list, data, bid); + } + } catch (e) { utils.logError(e); } + return true; +} + +export function getSegAndCatsArray(data, minScore) { + var sirdataData = {'segments': [], 'categories': []}; + minScore = minScore && typeof minScore == 'number' ? minScore : 30; + try { + if (data && data.contextual_categories) { + for (let catId in data.contextual_categories) { + let value = data.contextual_categories[catId]; + if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { + sirdataData.categories.push(catId.toString()); + } + } + } + } catch (e) { utils.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); } + return sirdataData; +} + +export function addSegmentData(adUnits, data, moduleConfig, onDone) { + moduleConfig = moduleConfig || {}; + moduleConfig.params = moduleConfig.params || {}; + 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; } + + const sirdataList = sirdataData.segments.concat(sirdataData.categories); + + var curationData = {'segments': [], 'categories': []}; + var curationId = '1'; + const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); + + // Global ortb2 + if (!biddersParamsExist) { + setGlobalOrtb2(sirdataData.segments, sirdataData.categories); + } + + // Google targeting + if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { + try { + // For curation Google is pid 27449 + curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + } + window.googletag.pubads().getSlots().forEach(function(n) { + if (typeof n.setTargeting !== 'undefined') { + n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + }) + } catch (e) { utils.logError(e); } + } + + // Bid targeting level for FPD non-generic biders + var bidderIndex = ''; + 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); + } + + adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { + bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function(i) { return i.bidder === bid.bidder; }) : false); + indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); + try { + curationData = {'segments': [], 'categories': []}; + let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore) + + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + // For curation Xandr is pid 27446 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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)); + } + break; + + case 'smartadserver': + case 'smart': + var target = []; + if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { + target.push(bid.params.target); + } + // For curation Smart is pid 27440 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); + } + }); + utils.deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'rubicon': + // For curation Magnite is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'ix': + var ixConfig = getGlobal().getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig) { + // For curation index is pid 27248 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + var cappIxCategories = []; + var ixLength = 0; + var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; + } + }); + getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + } + break; + + case 'proxistore': + // For curation Proxistore is pid 27484 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } else { + data.shared_taxonomy[curationId] = {contextual_categories: {}}; + } + 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}}); + } + break; + + case 'criteo': + // For curation Smart is pid 27443 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + setBidderOrtb2(bid.bidder, sirdataList.concat(curationData.segments).concat(curationData.categories), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'triplelift': + // For curation Triplelift is pid 27518 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'avct': + case 'avocet': + // For curation Avocet is pid 27522 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + case 'smaato': + // For curation Smaato is pid 27520 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + 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 { + setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + } + break; + + 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 (!utils.deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { + utils.deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + } + } + } + } + } catch (e) { utils.logError(e) } + }) + }); + + onDone(); + return adUnits; +} + +export function init(config) { + return true; +} + +export const sirdataSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getSegmentsAndCategories +}; + +submodule(MODULE_NAME, sirdataSubmodule); diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md new file mode 100644 index 00000000000..f67e34db43a --- /dev/null +++ b/modules/sirdataRtdProvider.md @@ -0,0 +1,169 @@ +# Sirdata Real-Time Data Submodule + +Module Name: Sirdata Rtd Provider +Module Type: Rtd Provider +Maintainer: bob@sirdata.com + +# Description + +Sirdata provides a disruptive API that allows its partners to leverage its +cutting-edge contextualization technology and its audience segments based on +cookies and consent or without cookies nor consent! + +User-based segments and page-level automatic contextual categories will be +attached to bid request objects sent to different SSPs in order to optimize +targeting. + +Automatic integration with Google Ad Manager and major bidders like Xandr/Appnexus, +Smartadserver, Index Exchange, Proxistore, Magnite/Rubicon or Triplelift ! + +User's country and choice management are included in the module, so it's 100% +compliant with local and regional laws like GDPR and CCPA/CPRA. + +ORTB2 compliant and FPD support for Prebid versions < 4.29 + +Contact bob@sirdata.com for information. + +### Publisher Usage + +Compile the Sirdata RTD module into your Prebid build: + +`gulp build --modules=rtdModule,sirdataRtdProvider` + +Add the Sirdata RTD provider to your Prebid config. + +Segments ids (user-centric) and category ids (page-centric) will be provided +salted and hashed : you can use them with a dedicated and private matching table. +Should you want to allow a SSP or a partner to curate your media and operate +cross-publishers campaigns with our data, please ask Sirdata (bob@sirdata.com) to +open it for you account. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + adUnitCodes: ['adUnit-1','adUnit-2'], + customFunction: overrideAppnexus, + curationId: '111', + },{ + bidder: 'ix', + sizeLimit: 1200 //specific to Index Exchange, + contextualMinRelevancyScore: 50, //Min score to filter contextual category for curation in the bidder (0-100 scale) + }] + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Sirdata Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | +| 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.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | +| params.setGptKeyValues | Boolean | This parameter Sirdata to set Targeting for GPT/GAM | Optional. Defaults to true. | +| params.contextualMinRelevancyScore | Integer | Min score to keep filter category in the bidders (0-100 scale). Optional. Defaults to 30. | +| params.bidders | Object | Dictionary of bidders you would like to supply Sirdata data for. | Optional. In case no bidder is specified Sirdata will atend to ad data custom and ortb2 to all bidders, adUnits & Globalconfig | + +Bidders can receive common setting : +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| bidder | String | Bidder name | Mandatory if params.bidders are specified | +| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered | +| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false | +| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at bob@sirdata.com | Optional. Default curation ids are specified for main bidders | +| contextualMinRelevancyScore | Integer | Min score to filter contextual categories for curation in the bidder (0-100 scale). Optional. Defaults to 30 or global params.contextualMinRelevancyScore if exits. | +| sizeLimit | Integer | used only for bidder 'ix' to limit the size of the get parameter in Index Exchange ad call | Optional. Default is 1000 | + + +### Overriding data sharing function +As indicated above, it is possible to provide your own bid augmentation +functions. This is useful if you know a bid adapter's API supports segment +fields which aren't specifically being added to request objects in the Prebid +bid adapter. + +Please see the following example, which provides a function to modify bids for +a bid adapter called ix and overrides the appnexus. + +data Object format for usage in this kind of function : +{ + "segments":[111111,222222], + "contextual_categories":{"333333":100}, + "shared_taxonomy":{ + "27446":{ //CurationId + "segments":[444444,555555], + "contextual_categories":{"666666":100} + } + } +} + +``` +function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) { + for (var i = 0; i < segmentsArray.length; i++) { + if (segmentsArray[i]) { + bid.params.user.segments.push(segmentsArray[i]); + } + } +} + +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories + bidders: [{ + bidder: 'appnexus', + customFunction: overrideAppnexus, + curationId: '111' + },{ + bidder: 'ix', + sizeLimit: 1200, //specific to Index Exchange + customFunction: function(adUnit, segmentsArray, dataObject, bid) { + bid.params.contextual.push(dataObject.contextual_categories); + }, + }] + } + } + ] + } + ... +} +``` + +### Testing + +To view an example of available segments returned by Sirdata's backends: + +`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html` \ No newline at end of file diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 20ab640990b..ffd242e57ac 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -1,6 +1,7 @@ /** - * This modules adds support for the new Size Mapping spec described here. https://github.com/prebid/Prebid.js/issues/4129 - * This implementation replaces global sizeConfig with a adUnit/bidder level sizeConfig with support for labels. + * This module adds support for the new size mapping spec, Advanced Size Mapping. It's documented here. https://github.com/prebid/Prebid.js/issues/4129 + * The implementation is an alternative to global sizeConfig. It introduces 'Ad Unit' & 'Bidder' level sizeConfigs and also supports 'labels' for conditional + * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ import * as utils from '../src/utils.js'; @@ -8,11 +9,9 @@ import { processNativeAdUnitParams } from '../src/native.js'; import { adunitCounter } from '../src/adUnits.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getHook } from '../src/hook.js'; -import { - adUnitSetupChecks -} from '../src/prebid.js'; +import { adUnitSetupChecks } from '../src/prebid.js'; -// allows for sinon.spy, sinon.stub, etc to unit test calls made to these functions internally +// Allows for stubbing of these functions while writing unit tests. export const internal = { checkBidderSizeConfigFormat, getActiveSizeBucket, @@ -22,9 +21,12 @@ export const internal = { isLabelActivated }; -// 'sizeMappingInternalStore' contains information whether a particular auction is using size mapping V2 (the new size mapping spec), -// and it also contains additional information on each adUnit, as such, mediaTypes, activeViewport, etc. -// This information is required by the 'getBids' function. +/* + 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), + and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by + the 'getBids' function. +*/ + export const sizeMappingInternalStore = createSizeMappingInternalStore(); function createSizeMappingInternalStore() { @@ -46,8 +48,10 @@ function createSizeMappingInternalStore() { } } -// returns "true" if atleast one of the adUnit in the adUnits array has declared a Ad Unit or(and) Bidder level sizeConfig -// returns "false" otherwise +/* + Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, + otherwise, returns "false." +*/ export function isUsingNewSizeMapping(adUnits) { let isUsingSizeMappingBool = false; adUnits.forEach(adUnit => { @@ -62,7 +66,7 @@ export function isUsingNewSizeMapping(adUnits) { }); // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids.forEach(bidder => { + adUnit.bids && utils.isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { if (bidder.sizeConfig) { if (isUsingSizeMappingBool === false) { isUsingSizeMappingBool = true; @@ -74,136 +78,179 @@ export function isUsingNewSizeMapping(adUnits) { return isUsingSizeMappingBool; } -// returns "adUnits" array which have passed sizeConfig validation checks in addition to mediaTypes checks -// deletes properties from adUnit which fail validation. +/** + This hooked function executes before the function 'checkAdUnitSetup', that is defined in /src/prebid.js. It's necessary to run this funtion before + because it applies a series of checks in order to determine the correctness of the 'sizeConfig' array, which, the original 'checkAdUnitSetup' function + does not recognize. + @params {Array} adUnits + @returns {Array} validateAdUnits - Unrecognized properties are deleted. +*/ export function checkAdUnitSetupHook(adUnits) { - return adUnits.filter(adUnit => { + const validateSizeConfig = function (mediaType, sizeConfig, adUnitCode) { + let isValid = true; + const associatedProperty = { + banner: 'sizes', + video: 'playerSize', + native: 'active' + } + const propertyName = associatedProperty[mediaType]; + const conditionalLogMessages = { + banner: 'Removing mediaTypes.banner from ad unit.', + video: 'Removing mediaTypes.video.sizeConfig from ad unit.', + native: 'Removing mediaTypes.native.sizeConfig from ad unit.' + } + if (Array.isArray(sizeConfig)) { + sizeConfig.forEach((config, index) => { + const keys = Object.keys(config); + /* + Check #1 (Applies to 'banner', 'video' and 'native' media types.) + Verify that all config objects include 'minViewPort' and 'sizes' property. + 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]}`); + isValid = false; + return; + } + /* + Check #2 (Applies to 'banner', 'video' and 'native' media types.) + 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]}`); + isValid = false + return; + } + /* + Check #3 (Applies only to 'banner' and 'video' media types.) + Verify that 'config.sizes' (in case of banner) or 'config.playerSize' (in case of video) + property is in [width, height] format. If not, return 'false'. + */ + if (mediaType === 'banner' || mediaType === 'video') { + let showError = false; + if (Array.isArray(config[propertyName])) { + const validatedSizes = adUnitSetupChecks.validateSizes(config[propertyName]); + if (config[propertyName].length > 0 && validatedSizes.length === 0) { + isValid = false; + showError = true; + } + } else { + // Either 'sizes' or 'playerSize' is not declared as an array, which makes it invalid by default. + isValid = false; + showError = true; + } + if (showError) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + return; + } + } + /* + Check #4 (Applies only to 'native' media type) + Verify that 'config.active' is a 'boolean'. + If not, return 'false'. + */ + if (mediaType === 'native') { + if (typeof config[propertyName] !== 'boolean') { + utils.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]}`); + isValid = false; + return isValid; + } + + // If all checks have passed, isValid should equal 'true' + return isValid; + } + const validatedAdUnits = []; + adUnits.forEach(adUnit => { + const bids = adUnit.bids; 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.`); + 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.`); - return false; + return; } - if (mediaTypes.banner) { - const banner = mediaTypes.banner; - if (banner.sizes) { - adUnitSetupChecks.validateBannerMediaType(adUnit); - } else if (banner.sizeConfig) { - if (Array.isArray(banner.sizeConfig)) { - let deleteBannerMediaType = false; - banner.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "sizes" property. - // if not, remove the mediaTypes.banner object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'sizes'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] is missing required property minViewPort or sizes or both.`); - deleteBannerMediaType = true; - return; - } - - // check if the config.sizes property is in [w, h] format, if yes, change it to [[w, h]] format. - const bannerSizes = adUnitSetupChecks.validateSizes(config.sizes); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (config.sizes.length > 0 && bannerSizes.length > 0) { - config.sizes = bannerSizes; - } else if (config.sizes.length === 0) { - // If a size bucket doesn't have any sizes, sizes is an empty array, i.e. sizes: []. This check takes care of that. - config.sizes = [config.sizes]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`); - deleteBannerMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteBannerMediaType = true; + if (mediaTypes.banner.sizes) { + // Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks! + 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); + const isBannerValid = validateSizeConfig('banner', mediaTypes.banner.sizeConfig, adUnit.code); + if (!isBannerValid) { + delete validatedBanner.mediaTypes.banner; + } else { + /* + Make sure 'sizes' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [360, 400] becomes [[360, 400]] + */ + validatedBanner.mediaTypes.banner.sizeConfig.forEach(config => { + if (!Array.isArray(config.sizes[0])) { + config.sizes = [config.sizes]; } }); - if (deleteBannerMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.banner has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.banner; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`); - delete adUnit.mediaTypes.banner; } } else { - utils.logError('Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.'); - delete adUnit.mediaTypes.banner; + // 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); + delete validatedBanner.mediaTypes.banner; } } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - adUnitSetupChecks.validateVideoMediaType(adUnit); - } else if (video.sizeConfig) { - if (Array.isArray(video.sizeConfig)) { - let deleteVideoMediaType = false; - video.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "playerSize" property. - // if not, remove the mediaTypes.video object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'playerSize'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - deleteVideoMediaType = true; - return; - } - // check if the config.playerSize property is in [w, h] format, if yes, change it to [[w, h]] format. - let tarPlayerSizeLen = (typeof config.playerSize[0] === 'number') ? 2 : 1; - const videoSizes = adUnitSetupChecks.validateSizes(config.playerSize, tarPlayerSizeLen); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); - } - if (config.playerSize.length > 0 && videoSizes.length > 0) { - config.playerSize = videoSizes; - } else if (config.playerSize.length === 0) { - // If a size bucket doesn't have any playerSize, playerSize is an empty array, i.e. playerSize: []. This check takes care of that. - config.playerSize = [config.playerSize]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`); - deleteVideoMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteVideoMediaType = true; + if (mediaTypes.video.playerSize) { + // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! + 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); + const isVideoValid = validateSizeConfig('video', mediaTypes.video.sizeConfig, adUnit.code); + if (!isVideoValid) { + delete validatedVideo.mediaTypes.video.sizeConfig; + } else { + /* + Make sure 'playerSize' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [640, 400] becomes [[640, 400]] + */ + validatedVideo.mediaTypes.video.sizeConfig.forEach(config => { + if (!Array.isArray(config.playerSize[0])) { + config.playerSize = [config.playerSize]; } }); - if (deleteVideoMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.video.sizeConfig; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.video.sizeConfig; } } } if (mediaTypes.native) { - const native = mediaTypes.native; - adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the old native checks + validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the new checks if 'mediaTypes.native.sizeConfig' detected if (mediaTypes.native.sizeConfig) { - native.sizeConfig.forEach(config => { - // verify if all config objects include "minViewPort" and "active" property. - // if not, remove the mediaTypes.native object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'active'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - - if (!(utils.isArrayOfNums(config.minViewPort, 2) && typeof config.active === 'boolean')) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - }); + const isNativeValid = validateSizeConfig('native', mediaTypes.native.sizeConfig, adUnit.code); + if (!isNativeValid) { + delete validatedNative.mediaTypes.native.sizeConfig; + } } } - return true; + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + validatedAdUnits.push(validatedAdUnit); }); + return validatedAdUnits; } getHook('checkAdUnitSetup').before(function (fn, adUnits) { diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js deleted file mode 100644 index d717f3a88bd..00000000000 --- a/modules/slimcutBidAdapter.js +++ /dev/null @@ -1,128 +0,0 @@ -import * as utils 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. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - 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) { - isValid = true; - } - return isValid; - }, - - /** - * 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 bids = validBidRequests.map(buildRequestObject); - const payload = { - referrer: getReferrerInfo(bidderRequest), - data: bids, - deviceWidth: screen.width - }; - - let gdpr = bidderRequest.gdprConsent; - if (bidderRequest && gdpr) { - let isCmp = (typeof gdpr.gdprApplies === 'boolean') - let isConsentString = (typeof gdpr.consentString === 'string') - payload.gdpr_iab = { - consent: isConsentString ? gdpr.consentString : '', - status: isCmp ? gdpr.gdprApplies : -1 - }; - } - - 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 A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, request) { - const bidResponses = []; - serverResponse = serverResponse.body; - - if (serverResponse.responses) { - serverResponse.responses.forEach(function (bid) { - const bidResponse = { - cpm: bid.cpm, - width: bid.width, - height: bid.height, - currency: bid.currency, - netRevenue: bid.netRevenue, - ttl: bid.ttl, - ad: bid.ad, - requestId: bid.requestId, - creativeId: bid.creativeId, - transactionId: bid.tranactionId, - winUrl: bid.winUrl - }; - bidResponses.push(bidResponse); - }); - } - return bidResponses; - }, - - getUserSyncs: function(syncOptions, serverResponses) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://sb.freeskreen.com/async_usersync.html' - }]; - } - 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); - reqObj.placementId = parseInt(placementId); - reqObj.adUnitCode = utils.getBidIdParameter('adUnitCode', bid); - reqObj.auctionId = utils.getBidIdParameter('auctionId', bid); - reqObj.transactionId = utils.getBidIdParameter('transactionId', bid); - - return reqObj; -} - -function getReferrerInfo(bidderRequest) { - let ref = window.location.href; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - ref = bidderRequest.refererInfo.referer; - } - return ref; -} - -registerBidder(spec); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js new file mode 100644 index 00000000000..700fcf17785 --- /dev/null +++ b/modules/smaatoBidAdapter.js @@ -0,0 +1,439 @@ +import * as utils 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 SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.4' +const CURRENCY = 'USD'; + +const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { + const requestTemplate = { + id: bidderRequest.auctionId, + at: 1, + cur: [CURRENCY], + tmax: bidderRequest.timeout, + site: { + id: window.location.hostname, + publisher: { + id: utils.deepAccess(bidRequest, 'params.publisherId') + }, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer + }, + device: { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, + ext: { + client: SMAATO_CLIENT + } + }; + + let ortb2 = config.getConfig('ortb2') || {}; + Object.assign(requestTemplate.user, ortb2.user); + Object.assign(requestTemplate.site, ortb2.site); + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { + utils.deepSetValue(requestTemplate, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(requestTemplate, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent !== undefined) { + utils.deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (utils.deepAccess(bidRequest, 'params.app')) { + const geo = utils.deepAccess(bidRequest, 'params.app.geo'); + utils.deepSetValue(requestTemplate, 'device.geo', geo); + const ifa = utils.deepAccess(bidRequest, 'params.app.ifa') + utils.deepSetValue(requestTemplate, 'device.ifa', ifa); + } + + const eids = utils.deepAccess(bidRequest, 'userIdAsEids'); + if (eids && eids.length) { + utils.deepSetValue(requestTemplate, 'user.ext.eids', eids); + } + + let requests = []; + + if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { + const bannerRequest = Object.assign({}, requestTemplate, createBannerImp(bidRequest)); + requests.push(bannerRequest); + } + + const videoMediaType = utils.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); + } + } + + return requests; +} + +const buildServerRequest = (validBidRequest, data) => { + utils.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 = { + 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: (bid) => { + if (typeof bid.params !== 'object') { + utils.logError('[SMAATO] Missing params object'); + return false; + } + + if (typeof bid.params.publisherId !== 'string') { + utils.logError('[SMAATO] Missing mandatory publisherId param'); + return false; + } + + if (utils.deepAccess(bid, 'mediaTypes.video.context') === ADPOD) { + utils.logInfo('[SMAATO] Verifying adpod bid request'); + + if (typeof bid.params.adbreakId !== 'string') { + utils.logError('[SMAATO] Missing for adpod request mandatory adbreakId param'); + return false; + } + + if (bid.params.adspaceId) { + utils.logError('[SMAATO] The adspaceId param is not allowed in an adpod bid request'); + return false; + } + } else { + utils.logInfo('[SMAATO] Verifying a non adpod bid request'); + + if (typeof bid.params.adspaceId !== 'string') { + utils.logError('[SMAATO] Missing mandatory adspaceId param'); + return false; + } + + if (bid.params.adbreakId) { + utils.logError('[SMAATO] The adbreakId param is only allowed in an adpod bid request'); + return false; + } + } + + utils.logInfo('[SMAATO] Verification done, all good'); + return true; + }, + + buildRequests: (validBidRequests, bidderRequest) => { + utils.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. + */ + interpretResponse: (serverResponse, bidRequest) => { + // response is empty (HTTP 204) + if (utils.isEmpty(serverResponse.body)) { + utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids'); + return []; // no bids + } + + const serverResponseHeaders = serverResponse.headers; + + const smtExpires = serverResponseHeaders.get('X-SMT-Expires'); + utils.logInfo('[SMAATO] Expires:', smtExpires); + const ttlInSec = smtExpires ? Math.floor((smtExpires - Date.now()) / 1000) : 300; + + const response = serverResponse.body; + utils.logInfo('[SMAATO] OpenRTB Response:', response); + + const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + const bids = []; + response.seatbid.forEach(seatbid => { + seatbid.bid.forEach(bid => { + let resultingBid = { + requestId: bid.impid, + cpm: bid.price || 0, + width: bid.w, + height: bid.h, + ttl: ttlInSec, + creativeId: bid.crid, + dealId: bid.dealid || null, + netRevenue: utils.deepAccess(bid, 'ext.net', true), + currency: response.cur, + meta: { + advertiserDomains: bid.adomain, + networkName: bid.bidderName, + agencyId: seatbid.seat + } + }; + + const videoContext = utils.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: + utils.logInfo('[SMAATO] Invalid ad type:', smtAdType); + } + } + resultingBid.meta.mediaType = resultingBid.mediaType; + }); + }); + + utils.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. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + return []; + } +} +registerBidder(spec); + +const createImgAd = (adm) => { + const image = JSON.parse(adm).image; + + let clickEvent = ''; + image.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
`; + + image.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; + +const createRichmediaAd = (adm) => { + const rich = JSON.parse(adm).richmedia; + let clickEvent = ''; + rich.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
${rich.mediadata.content}`; + + rich.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; + +function createBannerImp(bidRequest) { + const adUnitSizes = utils.getAdUnitSizes(bidRequest); + const sizes = adUnitSizes.map((size) => ({w: size[0], h: size[1]})); + return { + imp: [{ + id: bidRequest.bidId, + tagid: utils.deepAccess(bidRequest, 'params.adspaceId'), + bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), + banner: { + w: sizes[0].w, + h: sizes[0].h, + format: sizes + } + }] + }; +} + +function createVideoImp(bidRequest, videoMediaType) { + return { + imp: [{ + id: bidRequest.bidId, + tagid: utils.deepAccess(bidRequest, 'params.adspaceId'), + bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + 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 = utils.deepAccess(bidRequest, 'params.adbreakId') + const bce = config.getConfig('adpod.brandCategoryExclusion') + let imp = { + id: bidRequest.bidId, + tagid: tagid, + bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + 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 = utils.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 = utils.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 = utils.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 = utils.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 (!utils.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 new file mode 100644 index 00000000000..41e1c952f2a --- /dev/null +++ b/modules/smaatoBidAdapter.md @@ -0,0 +1,95 @@ +# Overview + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +# Description + +The Smaato adapter requires setup and approval from the Smaato team, even for existing Smaato publishers. Please reach out to your account team or prebid@smaato.com for more information. + +# Test Parameters + +For banner adunits: + +``` +var adUnits = [{ + "code": "banner-unit", + "mediaTypes": { + "banner": { + "sizes": [320, 50] + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + +For video adunits: + +``` +var adUnits = [{ + "code": "video unit", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480], + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 30, + "startdelay": 0, + "linearity": 1, + "protocols": [7], + "skip": 1, + "skipmin": 5, + "api": [7], + "ext": {"rewarded": 0} + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + +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 97dd43fc5ba..a7b609bca20 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,20 +1,16 @@ 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 { 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, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], /** @@ -43,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 @@ -51,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, @@ -76,28 +156,8 @@ 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') { - // Specific attributes for instream. - let playerSize = videoMediaType.playerSize[0]; - payload.isVideo = true; - 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; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } @@ -110,14 +170,31 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - var payloadString = JSON.stringify(payload); + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const bannerMediaType = utils.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 = utils.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; + }, []); }, /** @@ -131,7 +208,7 @@ export const spec = { const bidResponses = []; let response = serverResponse.body; try { - if (response) { + if (response && !response.isNoAd) { const bidRequest = JSON.parse(bidRequestString.data); let bidResponse = { @@ -143,13 +220,16 @@ export const spec = { dealId: response.dealId, currency: response.currency, netRevenue: response.isNetCpm, - ttl: response.ttl + ttl: response.ttl, + dspPixels: response.dspPixels, + meta: { advertiserDomains: response.adomain ? response.adomain : [] } }; - if (bidRequest.isVideo) { + if (bidRequest.mediaType === VIDEO) { bidResponse.mediaType = VIDEO; bidResponse.vastUrl = response.adUrl; bidResponse.vastXml = response.ad; + bidResponse.content = response.ad; } else { bidResponse.adUrl = response.adUrl; bidResponse.ad = response.ad; @@ -163,6 +243,31 @@ export const spec = { 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 (!utils.isFn(bid.getFloor)) { + return DEFAULT_FLOOR; + } + + const floor = bid.getFloor({ + currency: currency || 'USD', + mediaType: '*', + size: '*' + }); + + if (utils.isPlainObject(floor) && !isNaN(floor.floor)) { + return floor.floor; + } + + return DEFAULT_FLOOR; + }, + /** * User syncs. * @@ -172,11 +277,18 @@ 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 }); + } else if (syncOptions.pixelEnabled && serverResponses.length > 0 && serverResponses[0].body.dspPixels !== undefined) { + serverResponses[0].body.dspPixels.forEach(function(pixel) { + syncs.push({ + type: 'image', + url: pixel + }); + }); } return syncs; } diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md index c6f68363d7c..7a2381ac9f1 100644 --- a/modules/smartadserverBidAdapter.md +++ b/modules/smartadserverBidAdapter.md @@ -94,4 +94,73 @@ Please reach out to your Technical account manager for more information. } }] }; +``` + +## Outstream Video + +``` + var outstreamVideoAdUnit = { + code: 'test-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid + }); + }); + } + }, + bids: [{ + bidder: "smart", + params: { + domain: 'https://prg.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 85089, + bidfloor: 5, + video: { + protocol: 6, + startDelay: 1 + } + } + }] + }; +``` + +## 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 new file mode 100644 index 00000000000..2399a12f932 --- /dev/null +++ b/modules/smarticoBidAdapter.js @@ -0,0 +1,121 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; + +const SMARTICO_CONFIG = { + bidRequestUrl: 'https://trmads.eu/preBidRequest', + widgetUrl: 'https://trmads.eu/get', + method: 'POST' +} + +const BIDDER_CODE = 'smartico'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.token && bid.params.placementId); + }, + buildRequests: function (validBidRequests, bidderRequest) { + var i + var j + var bid + var bidParam + var bidParams = [] + var sizes + var frameWidth = Math.round(window.screen.width) + var frameHeight = Math.round(window.screen.height) + for (i = 0; i < validBidRequests.length; i++) { + bid = validBidRequests[i] + if (bid.sizes) { + sizes = bid.sizes + } else if (typeof (BANNER) != 'undefined' && bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes + } else if (frameWidth && frameHeight) { + sizes = [[frameWidth, frameHeight]] + } else { + sizes = [] + } + for (j = 0; j < sizes.length; j++) { + bidParam = { + token: bid.params.token || '', + bidId: bid.bidId, + 'banner-format-width': sizes[j][0], + 'banner-format-height': sizes[j][1] + } + if (bid.params.bannerFormat) { + bidParam['banner-format'] = bid.params.bannerFormat + } + if (bid.params.language) { + bidParam.language = bid.params.language + } + if (bid.params.region) { + bidParam.region = bid.params.region + } + if (bid.params.regions && (bid.params.regions instanceof String || (bid.params.regions instanceof Array && bid.params.regions.length))) { + bidParam.regions = bid.params.regions + if (bidParam.regions instanceof Array) { + bidParam.regions = bidParam.regions.join(',') + } + } + bidParams.push(bidParam) + } + } + + var ServerRequestObjects = { + method: SMARTICO_CONFIG.method, + url: SMARTICO_CONFIG.bidRequestUrl, + bids: validBidRequests, + data: {bidParams: bidParams, auctionId: bidderRequest.auctionId} + } + return ServerRequestObjects; + }, + interpretResponse: function (serverResponse, bidRequest) { + var i + var bid + var bidObject + var url + var html + var ad + var ads + var token + var language + var scriptId + var bidResponses = [] + 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 || '' + + language = bid.params.language || SMARTICO_CONFIG.language || '' + + scriptId = encodeURIComponent('smartico-widget-' + bid.params.placementId + '-' + i) + + url = SMARTICO_CONFIG.widgetUrl + '?token=' + encodeURIComponent(token) + '&auction-id=' + encodeURIComponent(bid.auctionId) + '&from-auction-buffer=1&own_session=1&ad=' + encodeURIComponent(ad.id) + '&scriptid=' + scriptId + (ad.bannerFormatAlias ? '&banner-format=' + encodeURIComponent(ad.bannerFormatAlias) : '') + (language ? '&language=' + encodeURIComponent(language) : '') + + html = ' + + +
+ + + `; + + return adcode; +} + +const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [BANNER], + isBidRequestValid(bid) { + // as per OneCode integration, bids without params are valid + return true; + }, + buildRequests(validBidRequests, bidderRequest) { + if ((!validBidRequests) || (validBidRequests.length < 1)) { + return false; + } + + 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 tmax = setOnAny(validBidRequests, 'params.tmax') ? parseInt(setOnAny(validBidRequests, 'params.tmax'), 10) : TMAX; + const pbver = '$prebid.version$'; + const testMode = setOnAny(validBidRequests, 'params.test') ? 1 : undefined; + + let ref; + + try { + if (W.self === W.top && document.referrer) { ref = document.referrer; } + } catch (e) { + } + + const payload = { + id: bidderRequest.auctionId, + site: { + id: siteId, + publisher: publisherId ? { id: publisherId } : undefined, + page, + domain, + ref + }, + imp: validBidRequests.map(slot => mapImpression(slot)), + tmax, + user: {}, + regs: {}, + test: testMode, + }; + + applyGdpr(bidderRequest, payload); + applyClientHints(payload); + + return { + method: 'POST', + url: `${BIDDER_URL}?cs=${cookieSupport()}&bdver=${BIDDER_VERSION}&pbver=${pbver}&inver=0`, + data: JSON.stringify(payload), + bidderRequest, + }; + }, + + interpretResponse(serverResponse, request) { + const { bidderRequest } = request; + const response = serverResponse.body; + const bids = []; + const site = JSON.parse(request.data).site; // get page and referer data from request + site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) + let seat; + + if (response.seatbid !== undefined) { + /* + Match response to request, by comparing bid id's + 'bidid-' prefix indicates oneCode (parameterless) request and response + */ + response.seatbid.forEach(seatbid => { + seat = seatbid.seat; + seatbid.bid.forEach(serverBid => { + // get data from bid response + 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 currentBidId = id && siteId ? id : 'bidid-' + bidId; + return currentBidId === impid; + })[0]; + + // get data from linked bidRequest + const { bidId, params } = bidRequest || {}; + + // get slot id for current bid + site.slot = params && params.id; + + if (ext) { + /* + bid response might include ext object containing siteId / slotId, as detected by OneCode + update site / slot data in this case + */ + const { siteid, slotid } = ext; + site.id = siteid || site.id; + site.slot = slotid || site.slot; + } + + if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { + // found a matching request; add this bid + + // store site data for future notification + oneCodeDetection[bidId] = [site.id, site.slot]; + + const bid = { + requestId: bidId, + creativeId: crid, + cpm: price, + currency: response.cur, + 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) { + bids.push(bid); + } + } else { + utils.logWarn('Discarding response - no matching request / site id', serverBid.impid); + } + }); + }); + } + + return bids; + }, + getUserSyncs(syncOptions) { + if (syncOptions.iframeEnabled && consentApiVersion != 1) { + return [{ + type: 'iframe', + url: `${SYNC_URL}?tcf=${consentApiVersion}`, + }]; + } else { + utils.logWarn('sspBC adapter requires iframe based user sync.'); + } + }, + + onTimeout(timeoutData) { + const payload = getNotificationPayload(timeoutData); + if (payload) { + payload.event = 'timeout'; + sendNotification(payload); + return payload; + } + }, + + onBidWon(bid) { + const payload = getNotificationPayload(bid); + if (payload) { + payload.event = 'bidWon'; + sendNotification(payload); + return payload; + } + }, +}; + +registerBidder(spec); + +export { + spec, +}; diff --git a/modules/sspBCBidAdapter.md b/modules/sspBCBidAdapter.md new file mode 100644 index 00000000000..1678f3be594 --- /dev/null +++ b/modules/sspBCBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +Module Name: sspBC Bidder Adapter +Module Type: Bidder Adapter +Maintainer: wojciech.bialy@grupawp.pl + +# Description + +Module that connects to Wirtualna Polska Media header bidding endpoint to fetch bids. +Only banner format is supported. +Supported currencies: USD, EUR, PLN + +Required parameters: +- none + +Optional parameters: +- site id +- adslot id +- publisher id +- domain +- page +- tmax +- test + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'sspBC', + params: { + id: "006", + siteId: "235911", + test: 1 + } + }] + } +]; +``` diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js new file mode 100644 index 00000000000..9d10092b2d8 --- /dev/null +++ b/modules/stroeerCoreBidAdapter.js @@ -0,0 +1,199 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const GVL_ID = 136; +const BIDDER_CODE = 'stroeerCore'; +const DEFAULT_HOST = 'hb.adscale.de'; +const DEFAULT_PATH = '/dsh'; +const DEFAULT_PORT = ''; +const FIVE_MINUTES_IN_SECONDS = 300; +const USER_SYNC_IFRAME_URL = 'https://js.adscale.de/pbsync.html'; + +const isSecureWindow = () => utils.getWindowSelf().location.protocol === 'https:'; +const isMainPageAccessible = () => getMostAccessibleTopWindow() === utils.getWindowTop(); + +function getTopWindowReferrer() { + try { + return utils.getWindowTop().document.referrer; + } catch (e) { + return utils.getWindowSelf().referrer; + } +} + +function getMostAccessibleTopWindow() { + let res = utils.getWindowSelf(); + + try { + while (utils.getWindowTop().top !== res && res.parent.location.href.length) { + res = res.parent; + } + } catch (ignore) { + } + + return res; +} + +function elementInView(elementId) { + const resolveElement = (elId) => { + const win = utils.getWindowSelf(); + + return win.document.getElementById(elId); + }; + + const visibleInWindow = (el, win) => { + const rect = el.getBoundingClientRect(); + const inView = (rect.top + rect.height >= 0) && (rect.top <= win.innerHeight); + + if (win !== win.parent) { + return inView && visibleInWindow(win.frameElement, win.parent); + } + + return inView; + }; + + try { + return visibleInWindow(resolveElement(elementId), utils.getWindowSelf()); + } catch (e) { + // old browser, element not found, cross-origin etc. + } + return undefined; +} + +function buildUrl({host: hostname = DEFAULT_HOST, port = DEFAULT_PORT, securePort, path: pathname = DEFAULT_PATH}) { + if (securePort) { + port = securePort; + } + + return utils.buildUrl({protocol: 'https', hostname, port, pathname}); +} + +function getGdprParams(gdprConsent) { + if (gdprConsent) { + const consentString = encodeURIComponent(gdprConsent.consentString || '') + const isGdpr = gdprConsent.gdprApplies ? 1 : 0; + + return `?gdpr=${isGdpr}&gdpr_consent=${consentString}` + } else { + return ''; + } +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (function () { + const validators = []; + + const createValidator = (checkFn, errorMsgFn) => { + return (bidRequest) => { + if (checkFn(bidRequest)) { + return true; + } else { + utils.logError(`invalid bid: ${errorMsgFn(bidRequest)}`, 'ERROR'); + return false; + } + } + }; + + function isBanner(bidReq) { + return (!bidReq.mediaTypes && !bidReq.mediaType) || + (bidReq.mediaTypes && bidReq.mediaTypes.banner) || + bidReq.mediaType === BANNER; + } + + validators.push(createValidator((bidReq) => isBanner(bidReq), + bidReq => `bid request ${bidReq.bidId} is not a banner`)); + validators.push(createValidator((bidReq) => typeof bidReq.params === 'object', + bidReq => `bid request ${bidReq.bidId} does not have custom params`)); + validators.push(createValidator((bidReq) => utils.isStr(bidReq.params.sid), + bidReq => `bid request ${bidReq.bidId} does not have a sid string field`)); + + return function (bidRequest) { + return validators.every(f => f(bidRequest)); + } + }()), + + buildRequests: function (validBidRequests = [], bidderRequest) { + const anyBid = bidderRequest.bids[0]; + + const payload = { + id: bidderRequest.auctionId, + bids: [], + ref: getTopWindowReferrer(), + ssl: isSecureWindow(), + mpa: isMainPageAccessible(), + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart) + }; + + const userIds = anyBid.userId; + + if (!utils.isEmpty(userIds)) { + payload.user = { + euids: userIds + }; + } + + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString != null && gdprConsent.gdprApplies != null) { + payload.gdpr = { + consent: bidderRequest.gdprConsent.consentString, applies: bidderRequest.gdprConsent.gdprApplies + }; + } + + function bidSizes(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes /* for prebid < 3 */ || []; + } + + validBidRequests.forEach(bid => { + payload.bids.push({ + bid: bid.bidId, sid: bid.params.sid, siz: bidSizes(bid), viz: elementInView(bid.adUnitCode) + }); + }); + + return { + method: 'POST', url: buildUrl(anyBid.params), data: payload + } + }, + + interpretResponse: function (serverResponse) { + const bids = []; + + if (serverResponse.body && typeof serverResponse.body === 'object') { + serverResponse.body.bids.forEach(bidResponse => { + bids.push({ + requestId: bidResponse.bidId, + cpm: bidResponse.cpm || 0, + width: bidResponse.width || 0, + height: bidResponse.height || 0, + ad: bidResponse.ad, + ttl: FIVE_MINUTES_IN_SECONDS, + currency: 'EUR', + netRevenue: true, + creativeId: '', + meta: { + advertiserDomains: bidResponse.adomain + }, + }); + }); + } + + return bids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + if (serverResponses.length > 0 && syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + getGdprParams(gdprConsent) + }]; + } + + return []; + } +}; + +registerBidder(spec); diff --git a/modules/stroeerCoreBidAdapter.md b/modules/stroeerCoreBidAdapter.md new file mode 100644 index 00000000000..fe6e92057c6 --- /dev/null +++ b/modules/stroeerCoreBidAdapter.md @@ -0,0 +1,31 @@ +## Overview + +``` +Module Name: Stroeer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: help@cz.stroeer-labs.com +``` + + +## Ad unit configuration for publishers + +```javascript +const adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'stroeerCore', + params: { + sid: "06b782cc-091b-4f53-9cd2-0291679aa1ac" + } + }] +}]; +``` +### Config Notes + +* Slot id (`sid`) is required. The adapter will ignore bid requests from prebid if `sid` is not provided. This must be in the decoded form. For example, "1234" as opposed to "MTM0ODA=". +* The server ignores dimensions that are not supported by the slot or by the platform (such as 987x123). diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js deleted file mode 100644 index 2200e01b089..00000000000 --- a/modules/stvBidAdapter.js +++ /dev/null @@ -1,150 +0,0 @@ - -import * as utils from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'stv'; -const VADS_ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; -const DEFAULT_VIDEO_SOURCE = 'vads'; -const DEFAULT_BANNER_FORMAT = 'vast2'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['vads'], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid: function(bid) { - return !!(bid.params.placement); - }, - 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 = sizes.split('x')[0]; - const height = sizes.split('x')[1]; - - const placementId = params.placement; - - const rnd = Math.floor(Math.random() * 99999999999); - const referrer = bidderRequest.refererInfo.referer; - const bidId = bidRequest.bidId; - let endpoint = VADS_ENDPOINT_URL; - - let payload = {}; - if (isVideoRequest(bidRequest)) { - const source = params.source || DEFAULT_VIDEO_SOURCE; - if (source === 'vads') { - payload = { - _f: 'vast2', - alternative: 'prebid_js', - _ps: placementId, - srw: width, - srh: height, - idt: 100, - rnd: rnd, - ref: referrer, - bid_id: bidId, - }; - endpoint = VADS_ENDPOINT_URL; - } - } else { - const outputFormat = params.format || DEFAULT_BANNER_FORMAT; - payload = { - _f: outputFormat, - alternative: 'prebid_js', - inventory_item_id: placementId, - srw: width, - srh: height, - idt: 100, - rnd: rnd, - ref: referrer, - bid_id: bidId, - }; - } - prepareExtraParams(params, payload); - - return { - method: 'GET', - url: endpoint, - data: objectToQueryString(payload), - } - }); - }, - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - const response = serverResponse.body; - const crid = response.crid || 0; - const cpm = response.cpm / 1000000 || 0; - if (cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; - const currency = response.currency || 'EUR'; - const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; - const bidResponse = { - requestId: response.bid_id, - cpm: cpm, - width: response.width, - height: response.height, - creativeId: crid, - dealId: dealId, - currency: currency, - netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout') - }; - - if (response.vastXml) { - bidResponse.vastXml = response.vastXml; - bidResponse.mediaType = 'video'; - } else { - bidResponse.ad = response.adTag; - } - - bidResponses.push(bidResponse); - } - return bidResponses; - } -} - -function objectToQueryString(obj, prefix) { - let str = []; - let p; - for (p in obj) { - if (obj.hasOwnProperty(p)) { - let k = prefix ? prefix + '[' + p + ']' : p; - let v = obj[p]; - str.push((v !== null && typeof v === 'object') - ? objectToQueryString(v, k) - : encodeURIComponent(k) + '=' + (k == '_ps' ? v : encodeURIComponent(v))); - } - } - return str.join('&'); -} - -/** - * 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' || !!utils.deepAccess(bid, 'mediaTypes.video'); -} - -function prepareExtraParams(params, payload) { - if (params.pfilter !== undefined) { - payload.pfilter = params.pfilter; - } - if (params.bcat !== undefined) { - payload.bcat = params.bcat; - } - if (params.noskip !== undefined) { - payload.noskip = params.noskip; - } - - if (params.dvt !== undefined) { - payload.dvt = params.dvt; - } -} - -registerBidder(spec); diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index 1f8cb59f442..480ab2bfed7 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -9,7 +9,33 @@ const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_TTL = 600; const SUBLIME_ANTENNA = 'antenna.ayads.co'; -const SUBLIME_VERSION = '0.5.2'; +const SUBLIME_VERSION = '0.8.0'; + +/** + * Identify the current device type + * @returns {string} + */ +function detectDevice() { + const isMobile = /(?:phone|windows\s+phone|ipod|blackberry|Galaxy Nexus|SM-G892A|(?:android|bbd+|meego|silk|googlebot) .+?mobile|palm|windows\s+ce|opera mini|avantgo|docomo)/i; + + const isTablet = /(?:ipad|playbook|Tablet|(?:android|bb\d+|meego|silk)(?! .+? mobile))/i; + + return ( + (isMobile.test(navigator.userAgent) && 'm') || // mobile + (isTablet.test(navigator.userAgent) && 't') || // tablet + 'd' // desktop + ); +} + +const UUID_V4_RE = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; +/** + * Checks whether a notifyId is well-formed + * @param {*} value + * @returns {boolean} + */ +function isValidNotifyId(value) { + return UUID_V4_RE.test(value); +} /** * Debug log message @@ -23,7 +49,9 @@ export function log(msg, obj) { // Default state export const state = { zoneId: '', - transactionId: '' + transactionId: '', + notifyId: '', + timeout: config.getConfig('bidderTimeout'), }; /** @@ -35,11 +63,32 @@ export function setState(value) { log('State has been updated :', state); } +/** + * Get a notifyId from bid params or from sublime global + * @param {Object} params - The bid params + * @return {string} + */ +function getNotifyId(params) { + const sublime = window.sublime = window.sublime || {}; + + let notifyId = params.notifyId || sublime.notifyId; + if (!notifyId) { + notifyId = utils.generateUUID(); + log('generating a notifyId', notifyId); + } + if (!sublime.notifyId) { + sublime.notifyId = notifyId; + } + + return notifyId; +} + /** * Send pixel to our debug endpoint * @param {string} eventName - Event name that will be send in the e= query string + * @param {string} [sspName] - The optionnal name of the AD provider */ -export function sendEvent(eventName) { +export function sendEvent(eventName, sspName) { const ts = Date.now(); const eventObject = { t: ts, @@ -47,11 +96,18 @@ export function sendEvent(eventName) { z: state.zoneId, e: eventName, src: 'pa', - puid: state.transactionId, - trId: state.transactionId, - ver: SUBLIME_VERSION, + puid: state.transactionId || state.notifyId, + notid: state.notifyId || '', + pbav: SUBLIME_VERSION, + pubtimeout: state.timeout, + pubpbv: '$prebid.version$', + device: detectDevice(), }; + if (eventName === 'bidwon') { + eventObject.sspname = sspName || ''; + } + log('Sending pixel for event: ' + eventName, eventObject); const queryString = utils.formatQS(eventObject); @@ -65,6 +121,16 @@ export function sendEvent(eventName) { * @return {Boolean} True if this is a valid bid, and false otherwise. */ function isBidRequestValid(bid) { + const notifyId = getNotifyId(bid.params); + if (!isValidNotifyId(notifyId)) { + log(`invalid notifyId format, got "${notifyId}"`); + return false; + } + if (notifyId !== window.sublime.notifyId) { + log(`notifyId mismatch: params [${bid.params.notifyId}] / sublime [${window.sublime.notifyId}]`); + return false; + } + return !!Number(bid.params.zoneId); } @@ -84,6 +150,8 @@ function buildRequests(validBidRequests, bidderRequest) { timeout: (typeof bidderRequest === 'object' && !!bidderRequest) ? bidderRequest.timeout : config.getConfig('bidderTimeout'), }; + setState({ timeout: commonPayload.timeout }); + // RefererInfo if (bidderRequest && bidderRequest.refererInfo) { commonPayload.referer = bidderRequest.refererInfo.referer; @@ -99,8 +167,11 @@ function buildRequests(validBidRequests, bidderRequest) { const bidHost = bid.params.bidHost || DEFAULT_BID_HOST; const protocol = bid.params.protocol || DEFAULT_PROTOCOL; + const notifyId = getNotifyId(bid.params); + setState({ transactionId: bid.transactionId, + notifyId, zoneId: bid.params.zoneId, debug: bid.params.debug || false, }); @@ -117,6 +188,7 @@ function buildRequests(validBidRequests, bidderRequest) { h: size[1], })), transactionId: bid.transactionId, + notifyId, zoneId: bid.params.zoneId, }; @@ -125,10 +197,10 @@ function buildRequests(validBidRequests, bidderRequest) { return { method: 'POST', url: protocol + '://' + bidHost + '/bid', - data: payload, + data: JSON.stringify(payload), options: { - contentType: 'application/json', - withCredentials: true + contentType: 'text/plain', + withCredentials: false }, } }); @@ -176,9 +248,16 @@ function interpretResponse(serverResponse, bidRequest) { netRevenue: response.netRevenue || true, ttl: response.ttl || DEFAULT_TTL, ad: response.ad, - pbav: SUBLIME_VERSION + pbav: SUBLIME_VERSION, + sspname: response.sspname || null }; + // We don't support advertiserDomains atm + if (response.advertiserDomains) { + // Creating a stub for Prebid.js 5.0 compliance + bidResponse.meta = Object.assign({}, bidResponse.meta, { advertiserDomains: [] }); + } + bidResponses.push(bidResponse); } @@ -187,19 +266,25 @@ function interpretResponse(serverResponse, bidRequest) { /** * Send pixel when bidWon event is triggered - * @param {Object} timeoutData + * @param {Object} bid */ function onBidWon(bid) { log('Bid won', bid); - sendEvent('bidwon'); + sendEvent('bidwon', bid.sspname); } /** * Send debug when we timeout - * @param {Object} timeoutData + * @param {Array[{}]} timeoutData */ function onTimeout(timeoutData) { log('Timeout from adapter', timeoutData); + + const timeout = utils.deepAccess(timeoutData, '0.timeout'); + if (timeout) { + // Set timeout to the one we got from the bid + setState({ timeout }); + } sendEvent('bidtimeout'); } @@ -207,11 +292,18 @@ export const spec = { code: BIDDER_CODE, gvlid: BIDDER_GVLID, aliases: [], - isBidRequestValid: isBidRequestValid, - buildRequests: buildRequests, - interpretResponse: interpretResponse, - onBidWon: onBidWon, - onTimeout: onTimeout, + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + onTimeout, + // Exposed for test purpose + sendEvent, + setState, + state, + detectDevice, + getNotifyId, + isValidNotifyId, }; registerBidder(spec); diff --git a/modules/sublimeBidAdapter.md b/modules/sublimeBidAdapter.md index e57f4a1fdb0..5cd1c95b682 100644 --- a/modules/sublimeBidAdapter.md +++ b/modules/sublimeBidAdapter.md @@ -9,7 +9,7 @@ Maintainer: pbjs@sublimeskinz.com # Description Connects to Sublime for bids. -Sublime bid adapter supports Skinz and M-Skinz formats. +Sublime bid adapter supports Skinz. # Nota Bene @@ -53,10 +53,13 @@ var adUnits = [{ bids: [{ bidder: 'sublime', params: { - zoneId: + zoneId: , + notifyId: } }] }]; ``` -Where you replace `` by your Sublime Zone id +Where you replace: +- `` by your Sublime Zone id; +- `` by your Sublime Notify id diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 8f069f551ee..7694f5d838e 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,18 +1,25 @@ 'use strict'; -import { getAdUnitSizes, logWarn } from '../src/utils.js'; +import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import includes from 'core-js-pure/features/array/includes.js'; import {config} from '../src/config.js'; -const BID_HOST = 'https://prebid.technoratimedia.com'; +const BID_SCHEME = 'https://'; +const BID_DOMAIN = 'technoratimedia.com'; const USER_SYNC_HOST = 'https://ad-cdn.technoratimedia.com'; const VIDEO_PARAMS = [ 'minduration', 'maxduration', 'startdelay', 'placement', 'linearity', 'mimes', 'protocols', 'api' ]; const BLOCKED_AD_SIZES = [ '1x1', '1x2' ]; +const SUPPORTED_USER_ID_SOURCES = [ + 'liveramp.com', // Liveramp IdentityLink + 'nextroll.com', // NextRoll XID + 'verizonmedia.com', // Verizon Media ConnectID + 'pubcid.org' // PubCommon ID +]; export const spec = { code: 'synacormedia', supportedMediaTypes: [ BANNER, VIDEO ], @@ -23,7 +30,7 @@ export const spec = { bid.mediaTypes.hasOwnProperty('video'); }, isBidRequestValid: function(bid) { - const hasRequiredParams = bid && bid.params && bid.params.hasOwnProperty('placementId') && bid.params.hasOwnProperty('seatId'); + const hasRequiredParams = bid && bid.params && (bid.params.hasOwnProperty('placementId') || bid.params.hasOwnProperty('tagId')) && bid.params.hasOwnProperty('seatId'); const hasAdSizes = bid && getAdUnitSizes(bid).filter(size => BLOCKED_AD_SIZES.indexOf(size.join('x')) === -1).length > 0 return !!(hasRequiredParams && hasAdSizes); }, @@ -36,7 +43,7 @@ export const spec = { const openRtbBidRequest = { id: bidderRequest.auctionId, site: { - domain: location.hostname, + domain: config.getConfig('publisherDomain') || location.hostname, page: refererInfo.referer, ref: document.referrer }, @@ -60,7 +67,7 @@ export const spec = { } else { seatId = bid.params.seatId; } - const placementId = bid.params.placementId; + const tagIdOrplacementId = bid.params.tagId || bid.params.placementId; const bidFloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : null; if (isNaN(bidFloor)) { logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); @@ -76,19 +83,32 @@ export const spec = { let imps = []; if (videoOrBannerKey === 'banner') { - imps = this.buildBannerImpressions(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey); + imps = this.buildBannerImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey); } else if (videoOrBannerKey === 'video') { - imps = this.buildVideoImpressions(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey); + imps = this.buildVideoImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey); } if (imps.length > 0) { imps.forEach(i => openRtbBidRequest.imp.push(i)); } }); + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // User ID + if (validBidReqs[0] && validBidReqs[0].userIdAsEids && Array.isArray(validBidReqs[0].userIdAsEids)) { + const eids = this.processEids(validBidReqs[0].userIdAsEids); + if (eids.length) { + deepSetValue(openRtbBidRequest, 'user.ext.eids', eids); + } + } + if (openRtbBidRequest.imp.length && seatId) { return { method: 'POST', - url: `${BID_HOST}/openrtb/bids/${seatId}?src=$$REPO_AND_VERSION$$`, + url: `${BID_SCHEME}${seatId}.${BID_DOMAIN}/openrtb/bids/${seatId}?src=$$REPO_AND_VERSION$$`, data: openRtbBidRequest, options: { contentType: 'application/json', @@ -98,7 +118,17 @@ export const spec = { } }, - buildBannerImpressions: function(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey) { + processEids: function(userIdAsEids) { + const eids = []; + userIdAsEids.forEach(function(eid) { + if (SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) > -1) { + eids.push(eid); + } + }); + return eids; + }, + + buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) { let format = []; let imps = []; adSizes.forEach((size, i) => { @@ -119,7 +149,7 @@ export const spec = { format, pos }, - tagid: placementId, + tagid: tagIdOrPlacementId, }; if (bidFloor !== null && !isNaN(bidFloor)) { imp.bidfloor = bidFloor; @@ -129,7 +159,7 @@ export const spec = { return imps; }, - buildVideoImpressions: function(adSizes, bid, placementId, pos, bidFloor, videoOrBannerKey) { + buildVideoImpressions: function(adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) { let imps = []; adSizes.forEach((size, i) => { if (!size || size.length != 2) { @@ -139,7 +169,7 @@ export const spec = { const size1 = size[1]; const imp = { id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`, - tagid: placementId + tagid: tagIdOrPlacementId }; if (bidFloor !== null && !isNaN(bidFloor)) { imp.bidfloor = bidFloor; @@ -151,6 +181,9 @@ export const spec = { pos }; if (bid.mediaTypes.video) { + if (!bid.params.video) { + bid.params.video = {}; + } this.setValidVideoParams(bid.mediaTypes.video, bid.params.video); } if (bid.params.video) { @@ -167,7 +200,7 @@ export const spec = { .filter(param => includes(VIDEO_PARAMS, param) && sourceObj[param] !== null && (!isNaN(parseInt(sourceObj[param], 10)) || !(sourceObj[param].length < 1))) .forEach(param => destObj[param] = Array.isArray(sourceObj[param]) ? sourceObj[param] : parseInt(sourceObj[param], 10)); }, - interpretResponse: function(serverResponse) { + interpretResponse: function(serverResponse, bidRequest) { const updateMacros = (bid, r) => { return r ? r.replace(/\${AUCTION_PRICE}/g, bid.price) : r; }; @@ -176,7 +209,6 @@ export const spec = { logWarn('Synacormedia: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return; } - const {id, seatbid: seatbids} = serverResponse.body; let bids = []; if (id && seatbids) { @@ -184,11 +216,35 @@ export const spec = { seatbid.bid.forEach(bid => { const creative = updateMacros(bid, bid.adm); const nurl = updateMacros(bid, bid.nurl); - const [, impType, impid, width, height] = bid.impid.match(/^([vb])(.*)-(.*)x(.*)$/); - const isVideo = impType == 'v'; + const [, impType, impid] = bid.impid.match(/^([vb])([\w\d]+)/); + let height = bid.h; + let width = bid.w; + const isVideo = impType === 'v'; + const isBanner = impType === 'b'; + if ((!height || !width) && bidRequest.data && bidRequest.data.imp && bidRequest.data.imp.length > 0) { + bidRequest.data.imp.forEach(req => { + if (bid.impid === req.id) { + if (isVideo) { + height = req.video.h; + width = req.video.w; + } else if (isBanner) { + let bannerHeight = 1; + let bannerWidth = 1; + if (req.banner.format && req.banner.format.length > 0) { + bannerHeight = req.banner.format[0].h; + bannerWidth = req.banner.format[0].w; + } + height = bannerHeight; + width = bannerWidth; + } else { + height = 1; + width = 1; + } + } + }); + } const bidObj = { requestId: impid, - adId: bid.id.replace(/~/g, '-'), cpm: parseFloat(bid.price), width: parseInt(width, 10), height: parseInt(height, 10), @@ -199,6 +255,11 @@ export const spec = { ad: creative, ttl: 60 }; + + if (bid.adomain != undefined || bid.adomain != null) { + bidObj.meta = { advertiserDomains: bid.adomain }; + } + if (isVideo) { const [, uuid] = nurl.match(/ID=([^&]*)&?/); if (!config.getConfig('cache.url')) { diff --git a/modules/synacormediaBidAdapter.md b/modules/synacormediaBidAdapter.md index fd71f07b3a3..523c66fd1d9 100644 --- a/modules/synacormediaBidAdapter.md +++ b/modules/synacormediaBidAdapter.md @@ -33,7 +33,7 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm bidder: "synacormedia", params: { seatId: "prebid", - placementId: "demo1", + tagId: "demo1", bidfloor: 0.10, pos: 1 } @@ -52,7 +52,7 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm bidder: "synacormedia", params: { seatId: "prebid", - placementId: "demo1", + tagId: "demo1", bidfloor: 0.20, pos: 1, video: { diff --git a/modules/tapadIdSystem.js b/modules/tapadIdSystem.js new file mode 100644 index 00000000000..a49d62897ad --- /dev/null +++ b/modules/tapadIdSystem.js @@ -0,0 +1,61 @@ +import { uspDataHandler } from '../src/adapterManager.js'; +import { submodule } from '../src/hook.js'; +import * as ajax from '../src/ajax.js' +import * as utils from '../src/utils.js'; + +export const graphUrl = 'https://rtga.tapad.com/v1/graph'; + +export const tapadIdSubmodule = { + name: 'tapadId', + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{tapadId: string} | undefined} + */ + decode(id) { + return { tapadId: id }; + }, + /* + * @function + * @summary initiate Real Time Graph + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} [consentData] + * @returns {IdResponse }} + */ + getId(config) { + const uspData = uspDataHandler.getConsentData(); + if (uspData && uspData !== '1---') { + return { id: undefined }; + } + const configParams = config.params || {}; + + if (configParams.companyId == null || isNaN(Number(configParams.companyId))) { + utils.logMessage('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + } + + return { + callback: (complete) => { + ajax.ajaxBuilder(10000)( + `${graphUrl}?company_id=${configParams.companyId}&tapad_id_type=TAPAD_ID`, + { + success: (response) => { + const responseJson = JSON.parse(response); + if (responseJson.hasOwnProperty('tapadId')) { + complete(responseJson.tapadId); + } + }, + error: (_, e) => { + if (e.status === 404) { + complete(undefined); + } + if (e.status === 403) { + utils.logMessage('Invalid Company Id. Contact prebid@tapad.com for assistance.'); + } + } + } + ); + } + } + } +} +submodule('userId', tapadIdSubmodule); diff --git a/modules/tapadIdSystem.md b/modules/tapadIdSystem.md new file mode 100644 index 00000000000..4bab1b4689d --- /dev/null +++ b/modules/tapadIdSystem.md @@ -0,0 +1,43 @@ +### Tapad ID + +Tapad's ID module provides access to a universal identifier that publishers, ad tech platforms and advertisers can use for data collection and collation without reliance on third-party cookies. +Tapad's ID module is free to use and promotes collaboration across the industry by facilitating interoperability between DSPs, SSPs and publishers. + +To register as an authorized user of the Tapad ID module, or for more information, documentation and access to Tapad’s Terms and Conditions please contact [prebid@tapad.com](mailto:prebid@tapad.com). + +Tapad’s Privacy landing page containing links to region-specific Privacy Notices may be found here: [https://tapad.com/privacy.html](https://tapad.com/privacy.html). + +Add it to your Prebid.js package with: + + +`gulp build --modules=userId,tapadIdSystem` + +#### Tapad ID Configuration + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | `"tapadId"` | `"tapadId"` | +| params | Required | Object | Details for Tapad initialization. | | +| params.company_id | Required | Number | Tapad Company Id provided by Tapad | 1234567890 | + +#### Tapad ID Example + +```js +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "tapadId", + params: { + companyId: 1234567890 + }, + storage: { + type: "cookie", + name: "tapad_id", + expires: 1 + } + } + ] + } +}); +``` diff --git a/modules/taphypeBidAdapter.js b/modules/taphypeBidAdapter.js deleted file mode 100644 index 65c5a728127..00000000000 --- a/modules/taphypeBidAdapter.js +++ /dev/null @@ -1,45 +0,0 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -export const spec = { - code: 'taphype', - isBidRequestValid: function (bid) { - return !!bid.params.placementId; - }, - buildRequests: function (bidRequests) { - const requests = bidRequests.map(function (bid) { - const params = { - placementId: bid.params.placementId, - url: encodeURIComponent(window.location.href), - size: bid.sizes[0][0] + 'x' + bid.sizes[0][1], - rnd: Math.random(), - bidId: bid.bidId, - }; - - return {method: 'GET', url: 'https://us-central1-taphype-internal.cloudfunctions.net/th-prebid', data: params, options: {withCredentials: false}} - }); - - return requests; - }, - interpretResponse: function (serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body || !serverResponse.body.ad) { - return []; - } - - const bid = serverResponse.body; - const sizes = bid.size.split(','); - - return [{ - requestId: bidRequest.data.bidId, - cpm: bid.price, - width: sizes[0], - height: sizes[1], - creativeId: bidRequest.data.bidId, - currency: bid.currency || 'USD', - netRevenue: true, - ad: bid.ad, - ttl: 360 - }]; - }, -}; - -registerBidder(spec); diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js new file mode 100644 index 00000000000..97acf929cde --- /dev/null +++ b/modules/tappxBidAdapter.js @@ -0,0 +1,569 @@ +'use strict'; + +import * as utils 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'; + +const BIDDER_CODE = 'tappx'; +const TTL = 360; +const CUR = 'USD'; +const TAPPX_BIDDER_VERSION = '0.1.0818'; +const TYPE_CNN = 'prebidjs'; +const LOG_PREFIX = '[TAPPX]: '; +const VIDEO_SUPPORT = ['instream', 'outstream']; + +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; +const VIDEO_CUSTOM_PARAMS = { + '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 +} + +var hostDomain; + +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 validBasic(bid) && validMediaType(bid) + }, + + /** + * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * Make a server request from the list of BidRequests. + * + * @param {*} validBidRequests + * @param {*} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let requests = []; + validBidRequests.forEach(oneValidRequest => { + requests.push(buildOneRequest(oneValidRequest, bidderRequest)); + }); + return requests; + }, + + /** + * Parse the response and generate one or more bid objects. + * + * @param {*} serverResponse + * @param {*} originalRequest + */ + interpretResponse: function(serverResponse, originalRequest) { + const responseBody = serverResponse.body; + if (!serverResponse.body) { + utils.logWarn(LOG_PREFIX, 'Empty response body HTTP 204, no bids'); + return []; + } + + const bids = []; + responseBody.seatbid.forEach(serverSeatBid => { + serverSeatBid.bid.forEach(serverBid => { + bids.push(interpretBid(serverBid, originalRequest)); + }); + }); + + return bids; + }, + + /** + * If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. + * + * @param {*} syncOptions + * @param {*} serverResponses + * @param {*} gdprConsent + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let url = `https://${hostDomain}/cs/usersync.php?`; + + // GDPR & CCPA + if (gdprConsent) { + url += '&gdpr_optin=' + (gdprConsent.gdprApplies ? 1 : 0); + url += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + url += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // SyncOptions + if (syncOptions.iframeEnabled) { + url += '&type=iframe' + return [{ + type: 'iframe', + url: url + }]; + } else { + url += '&type=img' + return [{ + type: 'image', + url: url + }]; + } + } +} + +function validBasic(bid) { + if (bid.params == null) { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory Tappx parameters.'); + return false; + } + + if (bid.params.tappxkey == null) { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory Tappxkey parameter.'); + return false; + } + + if (bid.params.host == null) { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory Host parameter.'); + return false; + } + + let classicEndpoint = true + if ((new RegExp(`^(vz.*|zz.*)\\.*$`, 'i')).test(bid.params.host)) { + classicEndpoint = false + } + + if (classicEndpoint && bid.params.endpoint == null) { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory endpoint Tappx parameters.'); + return false; + } + + return true; +} + +function validMediaType(bid) { + const video = utils.deepAccess(bid, 'mediaTypes.video'); + + // Video validations + if (typeof video !== 'undefined') { + if (VIDEO_SUPPORT.indexOf(video.context) === -1) { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory Tappx parameters for Video. Video context not supported.'); + return false; + } + if (typeof video.mimes == 'undefined') { + utils.logWarn(LOG_PREFIX, 'Please review the mandatory Tappx parameters for Video. Mimes param is mandatory.'); + return false; + } + } + + return true; +} + +/** + * Parse the response and generate one bid object. + * + * @param {object} serverBid Bid by OpenRTB 2.5 + * @returns {object} Prebid banner bidObject + */ +function interpretBid(serverBid, request) { + let bidReturned = { + requestId: request.bids.bidId, + cpm: serverBid.price, + currency: serverBid.cur ? serverBid.cur : CUR, + width: serverBid.w, + height: serverBid.h, + ttl: TTL, + creativeId: serverBid.crid, + netRevenue: true, + } + + if (typeof serverBid.dealId !== 'undefined') { bidReturned.dealId = serverBid.dealId } + + if (typeof request.bids.mediaTypes !== 'undefined' && typeof request.bids.mediaTypes.video !== 'undefined') { + bidReturned.vastXml = serverBid.adm; + bidReturned.vastUrl = serverBid.lurl; + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = VIDEO; + bidReturned.width = serverBid.w; + bidReturned.height = serverBid.h; + + if (request.bids.mediaTypes.video.context === 'outstream') { + const url = (serverBid.ext.purl) ? serverBid.ext.purl : false; + if (typeof url === 'undefined') { + utils.logWarn(LOG_PREFIX, 'Error getting player outstream from tappx'); + return false; + } + bidReturned.renderer = createRenderer(bidReturned, request, url); + } + } else { + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = BANNER; + } + + if (typeof bidReturned.adomain !== 'undefined' || bidReturned.adomain !== null) { + bidReturned.meta = { advertiserDomains: request.bids.adomain }; + } + + return bidReturned; +} + +/** +* Build and makes the request +* +* @param {*} validBidRequests +* @param {*} bidderRequest +* @return response ad +*/ +function buildOneRequest(validBidRequests, bidderRequest) { + let hostInfo = _getHostInfo(validBidRequests); + const ENDPOINT = hostInfo.endpoint; + hostDomain = hostInfo.domain; + + const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); + const MKTAG = utils.deepAccess(validBidRequests, 'params.mktag'); + const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); + const BIDEXTRA = utils.deepAccess(validBidRequests, 'params.ext'); + const bannerMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.video'); + + // let requests = []; + let payload = {}; + let publisher = {}; + let tagid; + let api = {}; + + // > App/Site object + if (utils.deepAccess(validBidRequests, 'params.app')) { + let app = {}; + app.name = utils.deepAccess(validBidRequests, 'params.app.name'); + app.bundle = utils.deepAccess(validBidRequests, 'params.app.bundle'); + app.domain = utils.deepAccess(validBidRequests, 'params.app.domain'); + publisher.name = utils.deepAccess(validBidRequests, 'params.app.publisher.name'); + publisher.domain = utils.deepAccess(validBidRequests, 'params.app.publisher.domain'); + tagid = `${app.name}_typeAdBanVid_${getOs()}`; + payload.app = app; + api[0] = utils.deepAccess(validBidRequests, 'params.api') ? utils.deepAccess(validBidRequests, 'params.api') : [3, 5]; + } else { + let bundle = _extractPageUrl(validBidRequests, bidderRequest); + let site = {}; + site.name = bundle; + site.domain = bundle; + publisher.name = bundle; + publisher.domain = bundle; + tagid = `${site.name}_typeAdBanVid_${getOs()}`; + payload.site = site; + } + // < App/Site object + + // > Imp object + let imp = {}; + let w; + let h; + + if (bannerMediaType) { + let banner = {}; + w = bannerMediaType.sizes[0][0]; + h = bannerMediaType.sizes[0][1]; + banner.w = w; + banner.h = h; + if ( + ((bannerMediaType.sizes[0].indexOf(480) >= 0) && (bannerMediaType.sizes[0].indexOf(320) >= 0)) || + ((bannerMediaType.sizes[0].indexOf(768) >= 0) && (bannerMediaType.sizes[0].indexOf(1024) >= 0))) { + banner.pos = 7 + } else { + banner.pos = 4 + } + + banner.api = api; + + let format = {}; + format[0] = {}; + format[0].w = w; + format[0].h = h; + banner.format = format; + + imp.banner = banner; + } + + if (typeof videoMediaType !== 'undefined') { + let video = {}; + + let videoParams = utils.deepAccess(validBidRequests, 'params.video'); + if (typeof videoParams !== 'undefined') { + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoParams.hasOwnProperty(key)) { + video[key] = _checkParamDataType(key, videoParams[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + } + + if ((video.w === undefined || video.w == null || video.w <= 0) || + (video.h === undefined || video.h == null || video.h <= 0)) { + w = videoMediaType.playerSize[0][0]; + h = videoMediaType.playerSize[0][1]; + video.w = w; + video.h = h; + } + + video.mimes = videoMediaType.mimes; + + imp.video = video; + } + + imp.id = validBidRequests.bidId; + imp.tagid = tagid; + imp.secure = 1; + + imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + if (utils.isFn(validBidRequests.getFloor)) { + try { + let floor = validBidRequests.getFloor({ + currency: CUR, + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + imp.bidfloor = floor.floor; + } else { + utils.logWarn(LOG_PREFIX, 'Currency not valid. Use only USD with Tappx.'); + } + } catch (e) { + utils.logWarn(LOG_PREFIX, e); + imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); // Be sure that we have an imp.bidfloor + } + } + + let bidder = {}; + bidder.endpoint = ENDPOINT; + bidder.host = hostInfo.url; + bidder.bidfloor = BIDFLOOR; + bidder.ext = (typeof BIDEXTRA == 'object') ? BIDEXTRA : undefined; + + imp.ext = {}; + imp.ext.bidder = bidder; + // < Imp object + + // > Device object + let device = {}; + // Mandatory + device.os = getOs(); + device.ip = 'peer'; + device.ua = navigator.userAgent; + device.ifa = validBidRequests.ifa; + + // Optional + device.h = screen.height; + device.w = screen.width; + device.dnt = utils.getDNT() ? 1 : 0; + device.language = getLanguage(); + device.make = navigator.vendor ? navigator.vendor : ''; + + let geo = {}; + geo.country = utils.deepAccess(validBidRequests, 'params.geo.country'); + // < Device object + + // > GDPR + let user = {}; + user.ext = {}; + + // Universal ID + let eidsArr = utils.deepAccess(validBidRequests, 'userIdAsEids'); + if (typeof eidsArr !== 'undefined') { + eidsArr = eidsArr.filter( + uuid => + (typeof uuid !== 'undefined' && uuid !== null) && + (typeof uuid.source == 'string' && uuid.source !== null) && + (typeof uuid.uids[0].id == 'string' && uuid.uids[0].id !== null) + ) + + if (typeof user !== 'undefined') { user.ext.eids = eidsArr } + }; + + let regs = {}; + regs.gdpr = 0; + if (!(bidderRequest.gdprConsent == null)) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { regs.gdpr = bidderRequest.gdprConsent.gdprApplies; } + if (regs.gdpr) { user.ext.consent = bidderRequest.gdprConsent.consentString; } + } + + // CCPA + regs.ext = {}; + if (!(bidderRequest.uspConsent == null)) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // COPPA compliance + if (config.getConfig('coppa') === true) { + regs.coppa = config.getConfig('coppa') === true ? 1 : 0; + } + // < GDPR + + // > Payload Ext + let payloadExt = {}; + payloadExt.bidder = {}; + payloadExt.bidder.tappxkey = TAPPXKEY; + payloadExt.bidder.mktag = MKTAG; + payloadExt.bidder.bcid = utils.deepAccess(validBidRequests, 'params.bcid'); + payloadExt.bidder.bcrid = utils.deepAccess(validBidRequests, 'params.bcrid'); + payloadExt.bidder.ext = (typeof BIDEXTRA == 'object') ? BIDEXTRA : {}; + if (typeof videoMediaType !== 'undefined') { + payloadExt.bidder.ext.pbvidtype = videoMediaType.context; + } + // < Payload Ext + + // > Payload + payload.id = validBidRequests.auctionId; + payload.test = utils.deepAccess(validBidRequests, 'params.test') ? 1 : 0; + payload.at = 1; + payload.tmax = bidderRequest.timeout ? bidderRequest.timeout : 600; + payload.bidder = BIDDER_CODE; + payload.imp = [imp]; + payload.user = user; + payload.ext = payloadExt; + + payload.device = device; + payload.regs = regs; + // < Payload + + let pbjsv = ($$PREBID_GLOBAL$$.version !== null) ? encodeURIComponent($$PREBID_GLOBAL$$.version) : -1; + + return { + method: 'POST', + url: `${hostInfo.url}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}&pbjsv=${pbjsv}`, + data: JSON.stringify(payload), + bids: validBidRequests + }; +} + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +function getOs() { + let ua = navigator.userAgent; + if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } +} + +export function _getHostInfo(validBidRequests) { + let domainInfo = {}; + let endpoint = utils.deepAccess(validBidRequests, 'params.endpoint'); + let hostParam = utils.deepAccess(validBidRequests, 'params.host'); + + domainInfo.domain = hostParam.split('/', 1)[0]; + + let regexNewEndpoints = new RegExp(`^(vz.*|zz.*)\\.[a-z]{3}\\.tappx\\.com$`, 'i'); + let regexClassicEndpoints = new RegExp(`^([a-z]{3}|testing)\\.[a-z]{3}\\.tappx\\.com$`, 'i'); + + if (regexNewEndpoints.test(domainInfo.domain)) { + domainInfo.newEndpoint = true; + domainInfo.endpoint = domainInfo.domain.split('.', 1)[0] + domainInfo.url = `https://${hostParam}` + } else if (regexClassicEndpoints.test(domainInfo.domain)) { + domainInfo.newEndpoint = false; + domainInfo.endpoint = endpoint + domainInfo.url = `https://${hostParam}${endpoint}` + } + + return domainInfo; +} + +function outstreamRender(bid, request) { + bid.renderer.push(() => { + window.tappxOutstream.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: { + content: bid.vastXml + } + }); + }); +} + +function createRenderer(bid, request, url) { + const rendererInst = Renderer.install({ + id: request.id, + url: url, + loaded: false + }); + + try { + rendererInst.setRender(outstreamRender); + } catch (err) { + utils.logWarn(LOG_PREFIX, 'Prebid Error calling setRender on renderer'); + } + + return rendererInst; +} + +export function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = utils.isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = utils.isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = utils.isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = utils.isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + utils.logWarn(LOG_PREFIX, errMsg); + return undefined; +} + +export function _extractPageUrl(validBidRequests, bidderRequest) { + let domainUrl = utils.deepAccess(validBidRequests, 'params.domainUrl'); + + if (typeof domainUrl == 'undefined' || domainUrl == null) { + domainUrl = config.getConfig('pageUrl') || utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); + } + + if (typeof domainUrl == 'undefined' || domainUrl == null) { + try { + domainUrl = window.top.document.head.querySelector('link[rel="canonical"][href]').getAttribute('href'); + } catch (error) { + domainUrl = undefined; + } + } + + try { + domainUrl = domainUrl.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/img)[0].replace(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?/img, ''); + } catch (error) { + domainUrl = undefined; + } + + if (typeof domainUrl == 'undefined' || domainUrl == null) { + domainUrl = window.location.hostname; + } + + return domainUrl; +} + +registerBidder(spec); diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md new file mode 100644 index 00000000000..677718c261c --- /dev/null +++ b/modules/tappxBidAdapter.md @@ -0,0 +1,94 @@ +# Overview +``` +Module Name: Tappx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@tappx.com +``` + +# Description +Module that connects to :tappx demand sources. +Suppots Banner and Video (instream and outstream). +Please use ```tappx``` as the bidder code. +Ads sizes available: [300,250], [320,50], [320,480], [480,320], [728,90], [768,1024], [1024,768] + +# Banner Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[320,50]] + } + }, + bids: [ + { + bidder: "tappx", + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-android-1234", + endpoint: "ZZ1234PBJS", + bidfloor: 0.005, + mktag: "123456", // Optional: tappx mktag + test: true, // Optional: for testing purposes + domainUrl: "www.example.com", // Optional: add domain site + ext: { // Optional: extra params + foo: "bar" + } + } + } + ] + } + ]; +``` + + +# Video Test Parameters +``` + var adUnits = [ + { + code: 'video-ad-div', + renderer: { + options: { + text: "Tappx instream Video" + } + }, + mediaTypes: { + video: { + context: %CONTEXT%, // Could be "instream" or "outstream" + mimes : [ "video/mp4", "application/javascript" ], + playerSize: [320, 250] + } + }, + bids: [{ + bidder: 'tappx', + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-desktop-1234", + endpoint: "VZ12TESTCTV", + bidfloor: 0.005, + mktag: "123456", // Optional: tappx mktag + test: true, // Optional: for testing purposes + domainUrl: "www.example.com", // Optional: add domain site + video: { // Optional + 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 + }, + ext: { // Optional: extra params + foo: "bar" + } + } + }] + } + ]; +``` diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index dbeed4aceae..5e6062e5483 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,16 +1,22 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -const utils = require('../src/utils.js'); +import {getStorageManager} from '../src/storageManager.js'; +import * as utils from '../src/utils.js'; + const BIDDER_CODE = 'teads'; +const GVL_ID = 132; const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request'; const gdprStatus = { GDPR_APPLIES_PUBLISHER: 12, 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, + gvlid: GVL_ID, supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. @@ -39,13 +45,18 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const bids = validBidRequests.map(buildRequestObject); + const payload = { referrer: getReferrerInfo(bidderRequest), pageReferrer: document.referrer, networkBandwidth: getConnectionDownLink(window.navigator), + timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, - hb_version: '$prebid.version$' + hb_version: '$prebid.version$', + ...getFLoCParameters(utils.deepAccess(validBidRequests, '0.userId.flocId')), + ...getUnifiedId2Parameter(utils.deepAccess(validBidRequests, '0.userId.uid2')), + ...getFirstPartyTeadsIdParameter() }; if (validBidRequests[0].schain) { @@ -54,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, @@ -67,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 }; }, /** @@ -96,6 +107,9 @@ export const spec = { currency: bid.currency, netRevenue: true, ttl: bid.ttl, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, ad: bid.ad, requestId: bid.bidId, creativeId: bid.creativeId, @@ -108,7 +122,7 @@ export const spec = { }); } return bidResponses; - }, + } }; function getReferrerInfo(bidderRequest) { @@ -123,11 +137,44 @@ function getConnectionDownLink(nav) { return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; } +function getTimeToFirstByte(win) { + const performance = win.performance || win.webkitPerformance || win.msPerformance || win.mozPerformance; + + const ttfbWithTimingV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart > 0 && + performance.getEntriesByType('navigation')[0].requestStart > 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + if (ttfbWithTimingV2) { + return ttfbWithTimingV2.toString(); + } + + const ttfbWithTimingV1 = performance && + performance.timing.responseStart && + performance.timing.requestStart && + performance.timing.responseStart > 0 && + performance.timing.requestStart > 0 && + performance.timing.responseStart - performance.timing.requestStart; + + return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; +} + 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; } @@ -136,7 +183,7 @@ function isGlobalConsent(gdprData, apiVersion) { ? (gdprData.hasGlobalScope || gdprData.hasGlobalConsent) : gdprData && apiVersion === 2 ? !gdprData.isServiceSpecific - : false + : false; } function buildRequestObject(bid) { @@ -170,13 +217,15 @@ function concatSizes(bid) { .reduce(function(acc, currSize) { if (utils.isArray(currSize)) { if (utils.isArray(currSize[0])) { - currSize.forEach(function (childSize) { acc.push(childSize) }) + currSize.forEach(function (childSize) { + acc.push(childSize); + }) } else { acc.push(currSize); } } return acc; - }, []) + }, []); } else { return bid.sizes; } @@ -186,4 +235,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 74c97f34b74..b2904045144 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -124,7 +124,7 @@ function getDefaultSrcPageUrl() { } function getEncodedValIfNotEmpty(val) { - return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; + return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; } /** @@ -276,7 +276,6 @@ function createBid(status, reqBid, response, width, height, bidderCode) { width: width, height: height, bidderCode: bidderCode, - adId: response.id, currency: 'USD', netRevenue: true, ttl: 300, @@ -284,6 +283,11 @@ function createBid(status, reqBid, response, width, height, bidderCode) { }); } + bid.meta = bid.meta || {}; + if (response && response.adomain && response.adomain.length > 0) { + bid.meta.advertiserDomains = response.adomain; + } + return bid; } diff --git a/modules/temedyaBidAdapter.js b/modules/temedyaBidAdapter.js new file mode 100644 index 00000000000..4577fb295cd --- /dev/null +++ b/modules/temedyaBidAdapter.js @@ -0,0 +1,144 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'temedya'; +const ENDPOINT_URL = 'https://adm.vidyome.com/'; +const ENDPOINT_METHOD = 'GET'; +const CURRENCY = 'TRY'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + /** + * 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.widgetId); + }, + /** + * 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(req => { + const mediaType = this._isBannerRequest(req) ? 'display' : NATIVE; + const data = { + wid: req.params.widgetId, + type: mediaType, + count: (req.params.count > 6 ? 6 : req.params.count) || 1, + mediaType: mediaType, + requestid: req.bidId + }; + if (mediaType === 'display') { + data.sizes = utils.parseSizesInput( + req.mediaTypes && req.mediaTypes.banner && req.mediaTypes.banner.sizes + ).join('|') + } + /** @type {ServerRequest} */ + return { + method: ENDPOINT_METHOD, + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(data), + options: { withCredentials: false, requestId: req.bidId, mediaType: mediaType } + }; + }); + }, + /** + * 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 && bidRequest.options.mediaType == NATIVE) { + bidResponse.ads.forEach(function(ad) { + bidResponses.push({ + requestId: bidRequest.options.requestId, + cpm: parseFloat(ad.assets.cpm) || 1, + width: 320, + height: 240, + creativeId: ad.assets.id, + currency: ad.currency || CURRENCY, + netRevenue: false, + mediaType: NATIVE, + ttl: 360, + native: { + title: ad.assets.title, + body: ad.assets.body || '', + icon: { + url: ad.assets.files[0], + width: 320, + height: 240 + }, + image: { + url: ad.assets.files[0], + width: 320, + height: 240 + }, + privacyLink: '', + clickUrl: ad.assets.click_url, + displayUrl: ad.assets.click_url, + cta: '', + sponsoredBy: ad.assets.sponsor || '', + impressionTrackers: [bidResponse.base.widget.impression + '&ids=' + ad.id + ':' + ad.assets.id], + }, + }); + }); + } else if (bidResponse && bidRequest.options.mediaType == 'display') { + bidResponse.ads.forEach(function(ad) { + let w = ad.assets.width || 300; + let h = ad.assets.height || 250; + let htmlTag = ''; + htmlTag += ''; + bidResponses.push({ + requestId: bidRequest.options.requestId, + cpm: parseFloat(ad.assets.cpm) || 1, + width: w, + height: h, + creativeId: ad.assets.id, + currency: ad.currency || CURRENCY, + netRevenue: false, + ttl: 360, + mediaType: BANNER, + ad: htmlTag + }); + }); + } + return bidResponses; + } catch (err) { + utils.logError(err); + return []; + } + }, + /** + * @param {BidRequest} req + * @return {boolean} + * @private + */ + _isBannerRequest(req) { + return !!(req.mediaTypes && req.mediaTypes.banner); + } +} +registerBidder(spec); diff --git a/modules/temedyaBidAdapter.md b/modules/temedyaBidAdapter.md new file mode 100644 index 00000000000..31ac4f3689b --- /dev/null +++ b/modules/temedyaBidAdapter.md @@ -0,0 +1,64 @@ +# Overview + +Module Name: TE Medya Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@temedya.com + +# Description + +Module that connects to TE Medya's demand sources. + +TE Medya supports Native and Banner. + + +# Test Parameters +# Native +``` + + var adUnits = [ + { + code:'tme_div_id', + mediaTypes:{ + native: { + title: { + required: true + } + } + }, + bids:[ + { + bidder: 'temedya', + params: { + widgetId: 753497, + count: 1 + } + } + ] + } + ]; +``` +# Test Parameters +# Banner +``` + + var adUnits = [ + { + code:'tme_div_id', + mediaTypes:{ + banner: { + banner: { + sizes:[300, 250] + } + } + }, + bids:[ + { + bidder: 'temedya', + params: { + widgetId: 753497 + } + } + ] + } + ]; +``` \ No newline at end of file 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..020a7d6f7f0 --- /dev/null +++ b/modules/timeoutRtdProvider.js @@ -0,0 +1,173 @@ + +import { submodule } from '../src/hook.js'; +import * as ajax from '../src/ajax.js'; +import * as utils 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 +}; + +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) { + utils.logInfo('Timeout rules', rules); + let timeoutModifier = 0; + let toAdd = 0; + + if (rules.includesVideo) { + const hasVideo = timeoutRtdFunctions.checkVideo(adUnits); + toAdd = rules.includesVideo[hasVideo] || 0; + utils.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 Object.entries(rules.numAdUnits)) { + const [lowerBound, upperBound] = rangeStr.split('-'); + if (parseInt(lowerBound) <= numAdUnits && numAdUnits <= parseInt(upperBound)) { + utils.logInfo(`Adding ${timeoutVal} to timeout for numAdUnits ${numAdUnits}`) + timeoutModifier += timeoutVal; + break; + } + } + } + } + + if (rules.deviceType) { + const deviceType = timeoutRtdFunctions.getDeviceType(); + toAdd = rules.deviceType[deviceType] || 0; + utils.logInfo(`Adding ${toAdd} to timeout for deviceType ${deviceType}`) + timeoutModifier += toAdd; + } + + if (rules.connectionSpeed) { + const connectionSpeed = timeoutRtdFunctions.getConnectionSpeed(); + toAdd = rules.connectionSpeed[connectionSpeed] || 0; + utils.logInfo(`Adding ${toAdd} to timeout for connectionSpeed ${connectionSpeed}`) + timeoutModifier += toAdd; + } + + utils.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) { + utils.logInfo('Timeout rtd config', config); + const timeoutUrl = utils.deepAccess(config, 'params.endpoint.url'); + if (timeoutUrl) { + utils.logInfo('Timeout url', timeoutUrl); + ajax.ajaxBuilder()(timeoutUrl, { + success: function(response) { + try { + const rules = JSON.parse(response); + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, rules); + } catch (e) { + utils.logError('Error parsing json response from timeout provider.') + } + callback(); + }, + error: function(errorStatus) { + utils.logError('Timeout request error!', errorStatus); + callback(); + } + }); + } else if (utils.deepAccess(config, 'params.rules')) { + timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, utils.deepAccess(config, 'params.rules')); + callback(); + } else { + utils.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/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..e9e030ce33f 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.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); } } diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index af904aedc11..e8d248eea03 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,19 +1,20 @@ -import { BANNER } from '../src/mediaTypes.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?'; let gdprApplies = true; let consentString = null; export const tripleliftAdapterSpec = { - + gvlid: GVLID, code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { - return (typeof bid.params.inventoryCode !== 'undefined'); + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function (bid) { + return typeof bid.params.inventoryCode !== 'undefined'; }, buildRequests: function(bidRequests, bidderRequest) { @@ -107,21 +108,31 @@ function _getSyncType(syncOptions) { function _buildPostBody(bidRequests) { let data = {}; let { schain } = bidRequests[0]; - data.imp = bidRequests.map(function(bid, index) { - return { + const globalFpd = _getGlobalFpd(); + + data.imp = bidRequests.map(function(bidRequest, index) { + let imp = { id: index, - tagid: bid.params.inventoryCode, - floor: _getFloor(bid), - banner: { - format: _sizes(bid.sizes) - } + tagid: bidRequest.params.inventoryCode, + floor: _getFloor(bidRequest) + }; + // remove the else to support multi-imp + if (_isInstreamBidRequest(bidRequest)) { + imp.video = _getORTBVideo(bidRequest); + } else if (bidRequest.mediaTypes.banner) { + imp.banner = { format: _sizes(bidRequest.sizes) }; }; + if (!utils.isEmpty(bidRequest.ortb2Imp)) { + imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); + } + return imp; }); let eids = [ - ...getUnifiedIdEids(bidRequests), - ...getIdentityLinkEids(bidRequests), - ...getCriteoEids(bidRequests) + ...getUnifiedIdEids([bidRequests[0]]), + ...getIdentityLinkEids([bidRequests[0]]), + ...getCriteoEids([bidRequests[0]]), + ...getPubCommonEids([bidRequests[0]]) ]; if (eids.length > 0) { @@ -130,21 +141,42 @@ function _buildPostBody(bidRequests) { }; } - if (schain) { - data.ext = { - schain - } + let ext = _getExt(schain, globalFpd); + + if (!utils.isEmpty(ext)) { + data.ext = ext; } return data; } +function _isInstreamBidRequest(bidRequest) { + if (!bidRequest.mediaTypes.video) return false; + if (!bidRequest.mediaTypes.video.context) return false; + if (bidRequest.mediaTypes.video.context.toLowerCase() === 'instream') { + return true; + } else { + return false; + } +} + +function _getORTBVideo(bidRequest) { + // give precedent to mediaTypes.video + let video = { ...bidRequest.params.video, ...bidRequest.mediaTypes.video }; + if (!video.w) video.w = video.playerSize[0][0]; + if (!video.h) video.h = video.playerSize[0][1]; + if (video.context === 'instream') video.placement = 1; + // clean up oRTB object + delete video.playerSize; + return video; +} + function _getFloor (bid) { let floor = null; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ currency: 'USD', - mediaType: 'banner', - size: _sizes(bid.sizes) + mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner', + size: '*' }); if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { @@ -154,20 +186,79 @@ function _getFloor (bid) { return floor !== null ? floor : bid.params.floor; } -function getUnifiedIdEids(bidRequests) { - return getEids(bidRequests, 'tdid', 'adserver.org', 'TDID'); +function _getGlobalFpd() { + const fpd = {}; + const context = {} + const user = {}; + const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + + const fpdContext = Object.assign({}, ortbData.context); + const fpdUser = Object.assign({}, ortbData.user); + + _addEntries(context, fpdContext); + _addEntries(user, fpdUser); + + if (!utils.isEmpty(context)) { + fpd.context = context; + } + if (!utils.isEmpty(user)) { + fpd.user = user; + } + return fpd; } -function getIdentityLinkEids(bidRequests) { - return getEids(bidRequests, 'idl_env', 'liveramp.com', 'idl'); +function _getAdUnitFpd(adUnitFpd) { + const fpd = {}; + const context = {}; + + _addEntries(context, adUnitFpd.ext); + + if (!utils.isEmpty(context)) { + fpd.context = context; + } + + return fpd; } -function getCriteoEids(bidRequests) { - return getEids(bidRequests, 'criteoId', 'criteo.com', 'criteoId'); +function _addEntries(target, source) { + if (!utils.isEmpty(source)) { + Object.keys(source).forEach(key => { + if (source[key] != null) { + target[key] = source[key]; + } + }); + } +} + +function _getExt(schain, fpd) { + let ext = {}; + if (!utils.isEmpty(schain)) { + ext.schain = { ...schain }; + } + if (!utils.isEmpty(fpd)) { + ext.fpd = { ...fpd }; + } + return ext; +} + +function getUnifiedIdEids(bidRequest) { + return getEids(bidRequest, 'tdid', 'adserver.org', 'TDID'); +} + +function getIdentityLinkEids(bidRequest) { + return getEids(bidRequest, 'idl_env', 'liveramp.com', 'idl'); } -function getEids(bidRequests, type, source, rtiPartner) { - return bidRequests +function getCriteoEids(bidRequest) { + return getEids(bidRequest, 'criteoId', 'criteo.com', 'criteoId'); +} + +function getPubCommonEids(bidRequest) { + return getEids(bidRequest, 'pubcid', 'pubcid.org', 'pubcid'); +} + +function getEids(bidRequest, type, source, rtiPartner) { + return bidRequest .map(getUserId(type)) // bids -> userIds of a certain type .filter((x) => !!x) // filter out null userIds .map(formatEid(source, rtiPartner)); // userIds -> eid objects @@ -207,10 +298,11 @@ function _buildResponseObject(bidderRequest, bid) { let height = bid.height || 1; let dealId = bid.deal_id || ''; let creativeId = bid.crid || ''; + let breq = bidderRequest.bids[bid.imp_id]; if (bid.cpm != 0 && bid.ad) { bidResponse = { - requestId: bidderRequest.bids[bid.imp_id].bidId, + requestId: breq.bidId, cpm: bid.cpm, width: width, height: height, @@ -221,7 +313,29 @@ function _buildResponseObject(bidderRequest, bid) { currency: 'USD', ttl: 300, tl_source: bid.tl_source, + meta: {} + }; + + if (_isInstreamBidRequest(breq)) { + bidResponse.vastXml = bid.ad; + bidResponse.mediaType = 'video'; }; + + if (bid.advertiser_name) { + bidResponse.meta.advertiserName = bid.advertiser_name; + } + + if (bid.adomain && bid.adomain.length) { + bidResponse.meta.advertiserDomains = bid.adomain; + } + + if (bid.tl_source && bid.tl_source == 'hdx') { + bidResponse.meta.mediaType = 'banner'; + } + + if (bid.tl_source && bid.tl_source == 'tlx') { + bidResponse.meta.mediaType = 'native'; + } }; return bidResponse; } diff --git a/modules/tripleliftBidAdapter.md b/modules/tripleliftBidAdapter.md index d5f88a2bece..03dcee3b980 100644 --- a/modules/tripleliftBidAdapter.md +++ b/modules/tripleliftBidAdapter.md @@ -58,5 +58,25 @@ var adUnits = [{ floor: 0 } }] +}, { + code: 'instream-div-1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'instream_test', + video: { + mimes: ['video/mp4'], + w: 640, + h: 480, + }, + } + }] }]; ``` diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index 2de7edbc04d..fadc952b5fb 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -79,6 +79,26 @@ export const spec = { return bidResponses; }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + var gdprParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'http://ads.momagic.com/jsp/usersync.jsp' + gdprParams + }); + } + return syncs; + } }; @@ -95,7 +115,7 @@ 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; diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index f412fa21a05..d99dbf0cd87 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -2,9 +2,11 @@ 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'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; -const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; +const ADDITIONAL_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -36,101 +38,209 @@ 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 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 (!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 || {}; - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; - if (keywords.length > 0) { - keywords.forEach(deleteValues); + 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; + 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; } - pageKeywords = keywords; } + let impObj = { + id: bidId && bidId.toString(), + tagid: uid.toString(), + ext: { + divid: adUnitCode && adUnitCode.toString() + } + }; - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; + if (!utils.isEmpty(keywords)) { + if (!pageKeywords) { + pageKeywords = keywords; + } + impObj.ext.bidder = { keywords }; } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); + + if (bidFloor) { + impObj.bidfloor = bidFloor; } - const slot = slotsMap[adUnitCode]; - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } }); - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' + const source = { + tid: auctionId && auctionId.toString(), + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } }; - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); + if (schain) { + source.ext.schain = schain; } - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; + 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 = utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; + const siteKeywords = utils.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 (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; } + request.regs.ext.us_privacy = uspConsent; } return { - method: 'GET', + method: 'POST', url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, + data: JSON.stringify(request), + bidsMap }; }, /** @@ -138,13 +248,12 @@ export const spec = { * * @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) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; let errorMessage; @@ -155,32 +264,42 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { - return [{ + const syncsPerBidder = config.getConfig('userSync.syncsPerBidder'); + let params = []; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + params.push(`us_privacy=${uspConsent}`); + } + const stringParams = params.join('&'); + const syncs = [{ type: 'image', - url: ADAPTER_SYNC_URL + url: ADAPTER_SYNC_URL + (stringParams ? `?${stringParams}` : '') }]; + if (syncsPerBidder > 1) { + syncs.push({ + type: 'image', + url: ADDITIONAL_SYNC_URL + (stringParams ? `&${stringParams}` : '') + }); + } + return syncs; } } } -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); @@ -192,67 +311,48 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { +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); 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: 'USD', - 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; - } + const { bidsMap } = bidRequest; + const bid = bidsMap[serverBid.impid]; - 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]; - } - } - }); + if (!errorMessage && bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: false, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid, + meta: { + advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + }, + }; + 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; } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + + bidResponses.push(bidResponse); } } if (errorMessage) { @@ -285,4 +385,140 @@ function createRenderer (bid, rendererParams, RendererConst) { return rendererInst; } +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + if (protocols && protocols.length) { + result.protocols = protocols; + } + + return result; +} + +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]); + + if (format.length) { + result.format = format + } + return result; +} + +function addSegments(name, segName, segments, data, bidConfigName) { + if (segments && segments.length) { + data.push({ + name: name, + segment: segments.map((seg) => { + return {name: segName, value: seg}; + }) + }); + } 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) => { + return {name: segName, value: seg}; + }) + }); + } + } +} + +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 + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bid.params.bidFloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: bid.sizes.map(([w, h]) => ({w, h})) + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index a72f1ba85aa..f29d47eaf36 100644 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -14,36 +14,55 @@ 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: {} }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1 + } + }, bids: [ { bidder: "trustx", @@ -54,4 +73,4 @@ TrustX Bid Adapter supports Banner and Video (instream and outstream). ] } ]; -``` \ No newline at end of file +``` 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/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index f9982edef36..69cbbc697fc 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -1,12 +1,14 @@ 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 @@ -14,6 +16,7 @@ const VIDEO_CONTEXT = { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, ENDPOINT: 'https://hb.aralego.com/header', supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -65,11 +68,12 @@ export const spec = { let bid = { requestId: bidRequest.bidId, cpm: ad.cpm || 0, - creativeId: ad.ad_id || bidRequest.params.adid, + creativeId: ad.crid || ad.ad_id || bidRequest.params.adid, dealId: ad.deal || null, - currency: 'USD', + currency: ad.currency || 'USD', netRevenue: true, - ttl: 1800 + ttl: 1800, + meta: {} }; if (bidRequest.params && bidRequest.params.bidfloor && ad.cpm && ad.cpm < bidRequest.params.bidfloor) { @@ -77,6 +81,10 @@ export const spec = { } if (ad.creative_type) { bid.mediaType = ad.creative_type; + bid.meta.mediaType = ad.creative_type; + } + if (ad.adomain) { + bid.meta.advertiserDomains = ad.adomain; } switch (ad.creative_type) { @@ -124,16 +132,19 @@ export const spec = { return [bid]; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent) { + let gdprApplies = (gdprConsent && gdprConsent.gdprApplies) ? '1' : ''; + let apiVersion = (gdprConsent) ? gdprConsent.apiVersion : ''; + let consentString = (gdprConsent) ? gdprConsent.consentString : ''; if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: 'https://cdn.aralego.net/ucfad/cookie/sync.html' + url: 'https://cdn.aralego.net/ucfad/cookie/sync.html' + getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent) }]; } else if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: 'https://sync.aralego.com/idSync' + url: 'https://sync.aralego.com/idSync' + getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent) }]; } } @@ -146,6 +157,22 @@ function transformSizes(requestSizes) { } } +function getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent) { + let param = '?'; + if (gdprApplies == '1') { + param = param + 'gdpr=1&'; + } + if (apiVersion == 1) { + param = param + 'euconsent=' + consentString + '&'; + } else if (apiVersion == 2) { + param = param + 'euconsent-v2=' + consentString + '&'; + } + if (uspConsent) { + param = param + 'usprivacy=' + uspConsent; + } + return (param == '?') ? '' : param; +} + function parseSizes(bid) { let params = bid.params; if (bid.mediaType === VIDEO) { @@ -184,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, @@ -199,17 +255,21 @@ 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; - bidData.u = window.top.location.href; + bidData.u = config.getConfig('publisherDomain') || window.top.location.href; bidData.xr = 0; } catch (e) { bidData.host = window.location.hostname; - bidData.u = document.referrer || window.location.href; + bidData.u = config.getConfig('publisherDomain') || bidderRequest.refererInfo.referrer || document.referrer || window.location.href; bidData.xr = 1; } @@ -253,11 +313,75 @@ function getRequestData(bid, bidderRequest) { } if (bidderRequest && bidderRequest.gdprConsent) { - Object.assign(bidData, { - gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - euconsent: bidderRequest.gdprConsent.consentString - }); + if (bidderRequest.gdprConsent.apiVersion == 1) { + Object.assign(bidData, { + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + euconsent: bidderRequest.gdprConsent.consentString + }); + } else if (bidderRequest.gdprConsent.apiVersion == 2) { + Object.assign(bidData, { + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + 'euconsent-v2': bidderRequest.gdprConsent.consentString + }); + } + } + + if (config.getConfig('coppa')) { + bidData.coppa = true; } return bidData; } + +function addUserId(bidData, userId) { + bidData['eids'] = ''; + utils._each(userId, (userIdObjectOrValue, userIdProviderKey) => { + switch (userIdProviderKey) { + case 'haloId': + if (userIdObjectOrValue.haloId) { + bidData[userIdProviderKey + 'haloId'] = userIdObjectOrValue.haloId; + } + if (userIdObjectOrValue.auSeg) { + bidData[userIdProviderKey + '_auSeg'] = userIdObjectOrValue.auSeg; + } + break; + case 'parrableId': + if (userIdObjectOrValue.eid) { + bidData[userIdProviderKey + '_eid'] = userIdObjectOrValue.eid; + } + break; + case 'id5id': + if (userIdObjectOrValue.uid) { + bidData[userIdProviderKey + '_uid'] = userIdObjectOrValue.uid; + } + if (userIdObjectOrValue.ext && userIdObjectOrValue.ext.linkType) { + bidData[userIdProviderKey + '_linkType'] = userIdObjectOrValue.ext.linkType; + } + break; + case 'uid2': + if (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: + bidData[userIdProviderKey] = userIdObjectOrValue; + break; + } + }); + + return bidData; +} diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js new file mode 100644 index 00000000000..053b57cb76d --- /dev/null +++ b/modules/uid2IdSystem.js @@ -0,0 +1,97 @@ +/** + * This module adds uid2 ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/uid2IdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'uid2'; +const GVLID = 887; +const LOG_PRE_FIX = 'UID2: '; +const ADVERTISING_COOKIE = '__uid2_advertising_token'; + +function readCookie() { + return storage.cookiesAreEnabled() ? storage.getCookie(ADVERTISING_COOKIE) : null; +} + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ADVERTISING_COOKIE) : null; +} + +function getStorage() { + return getStorageManager(GVLID, MODULE_NAME); +} + +const storage = getStorage(); + +const logInfo = createLogInfo(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + if (value) { + const bidIds = { + id: value + } + result.uid2 = bidIds; + logInfo('Decoded value ' + JSON.stringify(result)); + return result; + } + return undefined; +} + +/** @type {Submodule} */ +export const uid2IdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * Vendor id of Prebid + * @type {Number} + */ + gvlid: GVLID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{uid2:{ id: string }} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData + * @returns {uid2Id} + */ + getId(config, consentData) { + logInfo('Creating UID 2.0'); + let value = readCookie() || readFromLocalStorage(); + logInfo('The advertising token: ' + value); + return {id: value} + }, + +}; + +// Register submodule for userId +submodule('userId', uid2IdSubmodule); diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md new file mode 100644 index 00000000000..fa596b17584 --- /dev/null +++ b/modules/uid2IdSystem.md @@ -0,0 +1,24 @@ +## UID 2.0 User ID Submodule + +UID 2.0 ID Module. + +### Prebid Params + +Individual params may be set for the UID 2.0 Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'uid2' + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the UID 2.0 User ID Module integration. + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the UID20 module - `"uid2"` | `"uid2"` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the UID 2.0 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 | `{"uid2": { "id": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}}` | diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 8368077a627..6268774bc12 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -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 6ead453b622..14a765206b6 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,8 +2,9 @@ * Adapter to send bids to Undertone */ -import { parseUrl } from '../src/utils.js'; +import { deepAccess, parseUrl } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'undertone'; const URL = 'https://hb.undertone.com/hb'; @@ -73,6 +74,7 @@ function getBannerCoords(id) { export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { bid.params.publisherId = parseInt(bid.params.publisherId); @@ -120,8 +122,20 @@ export const spec = { sizes: bidReq.sizes, params: bidReq.params }; + const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + if (videoMediaType) { + bid.video = { + playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, + streamType: deepAccess(bidReq, 'mediaTypes.video.context') || null, + playbackMethod: deepAccess(bidReq, 'params.video.playbackMethod') || null, + maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, + skippable: deepAccess(bidReq, 'params.video.skippable') || null + }; + bid.mediaType = 'video'; + } payload['x-ut-hb-params'].push(bid); }); + return { method: 'POST', url: reqUrl, @@ -145,8 +159,14 @@ export const spec = { currency: bidRes.currency, netRevenue: bidRes.netRevenue, ttl: bidRes.ttl || 360, - ad: bidRes.ad + meta: { advertiserDomains: bidRes.adomain ? bidRes.adomain : [] } }; + if (bidRes.mediaType && bidRes.mediaType === 'video') { + bid.vastXml = bidRes.ad; + bid.mediaType = bidRes.mediaType; + } else { + bid.ad = bidRes.ad + } bids.push(bid); } }); diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 33e60ad2789..e74c4425626 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -1,13 +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 {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'; /** * Placement ID and Account ID are required. @@ -38,25 +39,19 @@ export const buildRequests = (validBidRequests, bidderRequest) => { * @returns {string} */ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { - utils.logInfo( - '[UNICORN] buildOpenRtbBidRequestPayload.validBidRequests:', - validBidRequests - ); - utils.logInfo( - '[UNICORN] buildOpenRtbBidRequestPayload.bidderRequest:', - bidderRequest - ); + utils.logInfo('[UNICORN] buildOpenRtbBidRequestPayload.validBidRequests:', validBidRequests); + utils.logInfo('[UNICORN] buildOpenRtbBidRequestPayload.bidderRequest:', bidderRequest); const imp = validBidRequests.map(br => { - const sizes = utils.parseSizesInput(br.sizes)[0]; return { id: br.bidId, banner: { - w: sizes.split('x')[0], - h: sizes.split('x')[1] + format: makeFormat(br.sizes), + w: br.sizes[0][0], + h: br.sizes[0][1] }, tagid: utils.deepAccess(br, 'params.placementId') || br.adUnitCode, secure: 1, - bidfloor: parseFloat(utils.deepAccess(br, 'params.bidfloorCpm') || 0) + bidfloor: parseFloat(0) }; }); const request = { @@ -84,7 +79,8 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { source: { ext: { stype: 'prebid_uncn', - bidder: BIDDER_CODE + bidder: BIDDER_CODE, + prebid_version: UNICORN_PB_VERSION } }, ext: { @@ -103,7 +99,7 @@ const interpretResponse = (serverResponse, request) => { if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - bids.push({ + var bid = { requestId: b.impid, cpm: b.price || 0, width: b.w, @@ -113,7 +109,13 @@ 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) }) }); } @@ -138,13 +140,21 @@ const getUid = () => { } }; +/** + * Make imp.banner.format + * @param {Array} arr + */ +const makeFormat = arr => arr.map((s) => { + return {w: s[0], h: s[1]}; +}); + export const spec = { code: BIDDER_CODE, aliases: ['uncn'], 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 f916030d643..bc033f37992 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -18,6 +18,10 @@ export const unifiedIdSubmodule = { * @type {string} */ name: MODULE_NAME, + /** + * required for the gdpr enforcement module + */ + gvlid: 21, /** * decode the stored id value for passing to bid requests * @function @@ -30,10 +34,11 @@ export const unifiedIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + 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'); return; diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 15fe0fefe8b..d04192acf15 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -1,136 +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 {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, - 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 = utils.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 = utils.deepAccess(bid, 'meta.mediaType'); + if (bidMediaType && bidMediaType.toLowerCase() === 'banner') { + bid.mediaType = BANNER; + parsedBidResponse = handleBannerBid(bid); + } else if (bidMediaType && bidMediaType.toLowerCase() === 'video') { + let context = utils.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) { + utils.logError(new Error('UnrulyBidAdapter: Missing ad config.')); + return; + } + + return bid; +}; + +const handleInStreamBid = (bid) => { + if (!(bid.vastUrl || bid.vastXml)) { + utils.logError(new Error('UnrulyBidAdapter: Missing vastUrl or vastXml config.')); + return; + } + + return bid; +}; + +const handleOutStreamBid = (bid) => { + const hasConfig = !!utils.deepAccess(bid, 'ext.renderer.config'); + const hasSiteId = !!utils.deepAccess(bid, 'ext.renderer.config.siteId'); + + if (!hasConfig) { + utils.logError(new Error('UnrulyBidAdapter: Missing renderer config.')); + return; + } + if (!hasSiteId) { + utils.logError(new Error('UnrulyBidAdapter: Missing renderer siteId.')); + return; + } + + const exchangeRenderer = utils.deepAccess(bid, 'ext.renderer'); + + configureUniversalTag(exchangeRenderer, bid.requestId); + configureRendererQueue(); + + const rendererInstance = Renderer.install(Object.assign({}, exchangeRenderer)); + + const rendererConfig = Object.assign( + {}, + bid, + { + renderer: rendererInstance, + adUnitCode: utils.deepAccess(bid, 'ext.adUnitCode') + } + ); + + rendererInstance.setRender(() => { + notifyRenderer(rendererConfig) + }); + + bid.renderer = bid.renderer || rendererInstance; + return bid; +}; + +const isMediaTypesValid = (bid) => { + const mediaTypeVideoData = utils.deepAccess(bid, 'mediaTypes.video'); + const mediaTypeBannerData = utils.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 = utils.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 = utils.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 d3f32fbdd31..0a8e94883b4 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -11,6 +11,12 @@ const USER_IDS_CONFIG = { atype: 1 }, + // naveggId + 'naveggId': { + source: 'navegg.com', + atype: 1 + }, + // pubCommonId 'pubcid': { source: 'pubcid.org', @@ -30,20 +36,48 @@ const USER_IDS_CONFIG = { // id5Id 'id5id': { + getValue: function(data) { + return data.uid + }, source: 'id5-sync.com', - atype: 1 + atype: 1, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } }, // parrableId - 'parrableid': { + 'parrableId': { source: 'parrable.com', - atype: 1 + atype: 1, + getValue: function(parrableId) { + if (parrableId.eid) { + return parrableId.eid; + } + if (parrableId.ccpaOptout) { + // If the EID was suppressed due to a non consenting ccpa optout then + // we still wish to provide this as a reason to the adapters + return ''; + } + return null; + }, + getUidExt: function(parrableId) { + const extendedData = utils.pick(parrableId, [ + 'ibaOptout', + 'ccpaOptout' + ]); + if (Object.keys(extendedData).length) { + return extendedData; + } + } }, // identityLink 'idl_env': { source: 'liveramp.com', - atype: 1 + atype: 3 }, // liveIntentId @@ -52,7 +86,7 @@ const USER_IDS_CONFIG = { return data.lipbid; }, source: 'liveintent.com', - atype: 1, + atype: 3, getEidExt: function(data) { if (Array.isArray(data.segments) && data.segments.length) { return { @@ -65,7 +99,13 @@ const USER_IDS_CONFIG = { // britepoolId 'britepoolid': { source: 'britepool.com', - atype: 1 + atype: 3 + }, + + // dmdId + 'dmdId': { + source: 'hcn.health', + atype: 3 }, // lotamePanoramaId @@ -74,42 +114,131 @@ const USER_IDS_CONFIG = { atype: 1, }, - // DigiTrust - 'digitrustid': { - getValue: function (data) { - var id = null; - if (data && data.data && data.data.id != null) { - id = data.data.id; - } - return id; - }, - source: 'digitru.st', - atype: 1 - }, - // criteo 'criteoId': { source: 'criteo.com', atype: 1 }, + // merkleId + 'merkleId': { + source: 'merkleinc.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + return (data && data.keyID) ? { + keyID: data.keyID + } : undefined; + } + }, + // NetId 'netId': { source: 'netid.de', atype: 1 }, - // sharedid - 'sharedid': { - source: 'sharedid.org', - atype: 1, + + // zeotapIdPlus + 'IDP': { + source: 'zeotap.com', + atype: 1 + }, + + // haloId + 'haloId': { + source: 'audigent.com', + atype: 1 + }, + + // quantcastId + 'quantcastId': { + source: 'quantcast.com', + atype: 1 + }, + + // nextroll + 'nextrollId': { + source: 'nextroll.com', + atype: 1 + }, + + // IDx + 'idx': { + source: 'idx.lat', + atype: 1 + }, + + // Verizon Media ConnectID + 'connectid': { + source: 'verizonmedia.com', + atype: 3 + }, + + // Neustar Fabrick + 'fabrickId': { + 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) { - return data.id; + return data.snowflake }, - getUidExt: function(data) { - return (data && data.third) ? { - third: data.third - } : undefined; + source: 'novatiq.com', + atype: 1 + }, + 'uid2': { + source: 'uidapi.com', + atype: 3, + getValue: function(data) { + 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 } }; @@ -150,11 +279,37 @@ export function createEidsArray(bidRequestUserId) { let eids = []; for (const subModuleKey in bidRequestUserId) { if (bidRequestUserId.hasOwnProperty(subModuleKey)) { - const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); - if (eid) { - eids.push(eid); + if (subModuleKey === 'pubProvidedId') { + eids = eids.concat(bidRequestUserId['pubProvidedId']); + } else { + const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); + if (eid) { + eids.push(eid); + } } } } return eids; } + +/** + * @param {SubmoduleContainer[]} submodules + */ +export function buildEidPermissions(submodules) { + let eidPermissions = []; + submodules.filter(i => utils.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')) { + eidPermissions.push( + { + source: USER_IDS_CONFIG[key].source, + bidders: i.config.bidders + } + ); + } + }); + }); + return eidPermissions; +} diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 60f450e9328..a8e73216327 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -1,7 +1,8 @@ ## Example of eids array generated by UserId Module. + ``` userIdAsEids = [ - { + { source: 'pubcid.org', uids: [{ id: 'some-random-id-value', @@ -21,13 +22,33 @@ userIdAsEids = [ }, { - source: 'id5-sync.com', + source: 'navegg.com', + uids: [{ + id: 'naveggId', + atype: 1 + }] + }, + + { + source: 'neustar.biz', uids: [{ id: 'some-random-id-value', atype: 1 }] }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 2, + abTestingControlGroup: false + } + }] + }, + { source: 'parrable.com', uids: [{ @@ -56,7 +77,7 @@ userIdAsEids = [ }, { - source: 'britepool.com', + source: 'merkleinc.com', uids: [{ id: 'some-random-id-value', atype: 1 @@ -64,13 +85,21 @@ userIdAsEids = [ }, { - source: 'digitru.st', + source: 'britepool.com', uids: [{ id: 'some-random-id-value', atype: 1 }] }, + { + source: 'hcn.health', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { source: 'criteo.com', uids: [{ @@ -86,14 +115,100 @@ userIdAsEids = [ atype: 1 }] }, + { - source: 'sharedid.org', + source: 'zeotap.com', uids: [{ id: 'some-random-id-value', - atype: 1, - ext: { - third: 'some-random-id-value' - } + atype: 1 + }] + }, + + { + source: 'nextroll.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'audigent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'quantcast.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + + { + source: 'verizonmedia.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'mediawallahscript.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'tapad.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'novatiq.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'uidapi.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'akamai.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'admixer.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'deepintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + { + source: 'kpuid.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 }] } ] diff --git a/modules/userId/index.js b/modules/userId/index.js index 1c5768edae1..a6a824fe89e 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -14,7 +14,7 @@ * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @name Submodule#getId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @param {(Object|undefined)} cacheIdObj * @return {(IdResponse|undefined)} A response object that contains id and/or callback. @@ -27,7 +27,8 @@ * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @name Submodule#extendId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData * @param {Object} storedId - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ @@ -37,7 +38,7 @@ * @summary decode a stored value for passing to bid requests * @name Submodule#decode * @param {Object|string} value - * @param {SubmoduleParams|undefined} configParams + * @param {SubmoduleConfig|undefined} config * @return {(Object|undefined)} */ @@ -48,6 +49,20 @@ * @type {string} */ +/** + * @property + * @summary use a predefined domain override for cookies or provide your own + * @name Submodule#domainOverride + * @type {(undefined|function)} + */ + +/** + * @function + * @summary Returns the root domain + * @name Submodule#findRootDomain + * @returns {string} + */ + /** * @typedef {Object} SubmoduleConfig * @property {string} name - the User ID submodule name (used to link submodule with config) @@ -83,8 +98,10 @@ * @property {(string|undefined)} publisherId - the unique identifier of the publisher in question * @property {(string|undefined)} ajaxTimeout - the number of milliseconds a resolution request can take before automatically being terminated * @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query - * @property {(string|undefined)} providedIdentifierName - defines the name of an identifier that can be found in local storage or in the cookie jar that can be sent along with the getId request. This parameter should be used whenever a customer is able to provide the most stable identifier possible * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests + * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs + * @property {(string|undefined)} emailHash - if provided, the hashed email address of a user + * @property {(string|undefined)} notUse3P - use to retrieve envelope from 3p endpoint */ /** @@ -108,22 +125,34 @@ * @property {(function|undefined)} callback - function that will return an id */ +/** + * @typedef {Object} RefreshUserIdsOptions + * @property {(string[]|undefined)} submoduleNames - submodules to refresh + */ + import find from 'core-js-pure/features/array/find.js'; -import {config} from '../../src/config.js'; +import { 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 { 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} from './eids.js'; +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 includes from 'core-js-pure/features/array/includes.js'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const DEFAULT_SYNC_DELAY = 500; const NO_AUCTION_DELAY = 0; +const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { + name: '_pbjs_userid_consent_data', + expires: 30 // 30 days expiration, which should match how often consent is refreshed by CMPs +}; +export const PBJS_USER_ID_OPTOUT_NAME = '_pbjs_id_optout'; export const coreStorage = getCoreStorageManager('userid'); /** @type {string[]} */ @@ -159,17 +188,23 @@ export function setSubmoduleRegistry(submodules) { } /** - * @param {SubmoduleStorage} storage + * @param {SubmoduleContainer} submodule * @param {(Object|string)} value */ -function setStoredValue(storage, value) { +export function setStoredValue(submodule, value) { + /** + * @type {SubmoduleStorage} + */ + const storage = submodule.config.storage; + const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; + try { const valueStr = utils.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'); + coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax', domainOverride); if (typeof storage.refreshInSeconds === 'number') { - coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr); + coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr, 'Lax', domainOverride); } } else if (storage.type === LOCAL_STORAGE) { coreStorage.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); @@ -183,6 +218,14 @@ function setStoredValue(storage, value) { } } +function setPrebidServerEidPermissions(initializedSubmodules) { + let setEidPermissions = getPrebidInternal().setEidPermissions; + if (typeof setEidPermissions === 'function' && utils.isArray(initializedSubmodules)) { + setEidPermissions(buildEidPermissions(initializedSubmodules)); + } +} + +/** /** * @param {SubmoduleStorage} storage * @param {String|undefined} key optional key of the value @@ -206,7 +249,7 @@ function getStoredValue(storage, key = undefined) { } } // support storing a string or a stringified object - if (typeof storedValue === 'string' && storedValue.charAt(0) === '{') { + if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); } } catch (e) { @@ -215,6 +258,70 @@ function getStoredValue(storage, key = undefined) { return storedValue; } +/** + * makes an object that can be stored with only the keys we need to check. + * excluding the vendorConsents object since the consentString is enough to know + * if consent has changed without needing to have all the details in an object + * @param consentData + * @returns {{apiVersion: number, gdprApplies: boolean, consentString: string}} + */ +function makeStoredConsentDataHash(consentData) { + const storedConsentData = { + consentString: '', + gdprApplies: false, + apiVersion: 0 + }; + + if (consentData) { + storedConsentData.consentString = consentData.consentString; + storedConsentData.gdprApplies = consentData.gdprApplies; + storedConsentData.apiVersion = consentData.apiVersion; + } + + return utils.cyrb53Hash(JSON.stringify(storedConsentData)); +} + +/** + * puts the current consent data into cookie storage + * @param consentData + */ +export function setStoredConsentData(consentData) { + try { + 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); + } +} + +/** + * get the stored consent data from local storage, if any + * @returns {string} + */ +function getStoredConsentData() { + try { + return coreStorage.getCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name); + } catch (e) { + utils.logError(e); + } +} + +/** + * 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 && + storedConsentData === makeStoredConsentDataHash(consentData) + ); +} + /** * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1) * @param {ConsentData} consentData @@ -235,21 +342,81 @@ function hasGDPRConsent(consentData) { return true; } +/** + * Find the root domain + * @param {string|undefined} fullDomain + * @return {string} + */ +export function findRootDomain(fullDomain = window.location.hostname) { + if (!coreStorage.cookiesAreEnabled()) { + return fullDomain; + } + + const domainParts = fullDomain.split('.'); + if (domainParts.length == 2) { + return fullDomain; + } + let rootDomain; + let continueSearching; + let startIndex = -2; + const TEST_COOKIE_NAME = `_rdc${Date.now()}`; + const TEST_COOKIE_VALUE = 'writeable'; + do { + rootDomain = domainParts.slice(startIndex).join('.'); + let expirationDate = new Date(utils.timestamp() + 10 * 1000).toUTCString(); + + // Write a test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + TEST_COOKIE_VALUE, + expirationDate, + 'Lax', + rootDomain, + undefined + ); + + // See if the write was successful + const value = coreStorage.getCookie(TEST_COOKIE_NAME, undefined); + if (value === TEST_COOKIE_VALUE) { + continueSearching = false; + // Delete our test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + '', + 'Thu, 01 Jan 1970 00:00:01 GMT', + undefined, + rootDomain, + undefined + ); + } else { + startIndex += -1; + continueSearching = Math.abs(startIndex) <= domainParts.length; + } + } while (continueSearching); + return rootDomain; +} + /** * @param {SubmoduleContainer[]} submodules * @param {function} cb - callback for after processing is done. */ function processSubmoduleCallbacks(submodules, cb) { - const done = cb ? utils.delayExecution(cb, submodules.length) : function() { }; - submodules.forEach(function(submodule) { + let done = () => {}; + if (cb) { + done = utils.delayExecution(() => { + clearTimeout(timeoutID); + cb(); + }, submodules.length); + } + submodules.forEach(function (submodule) { submodule.callback(function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { - setStoredValue(submodule.config.storage, idObj); + setStoredValue(submodule, idObj); } // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(idObj); + submodule.idObj = submodule.submodule.decode(idObj, submodule.config); } else { utils.logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } @@ -259,7 +426,6 @@ function processSubmoduleCallbacks(submodules, cb) { // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); - clearTimeout(timeoutID); } /** @@ -280,6 +446,26 @@ function getCombinedSubmoduleIds(submodules) { return combinedSubmoduleIds; } +/** + * This function will create a combined object for bidder with allowed subModule Ids + * @param {SubmoduleContainer[]} submodules + * @param {string} bidder + */ +function getCombinedSubmoduleIdsForBidder(submodules, bidder) { + if (!Array.isArray(submodules) || !submodules.length || !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) + .reduce((carry, i) => { + Object.keys(i.idObj).forEach(key => { + carry[key] = i.idObj[key]; + }); + return carry; + }, {}); +} + /** * @param {AdUnit[]} adUnits * @param {SubmoduleContainer[]} submodules @@ -288,21 +474,22 @@ function addIdDataToAdUnitBids(adUnits, submodules) { if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } - const combinedSubmoduleIds = getCombinedSubmoduleIds(submodules); - const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); - if (Object.keys(combinedSubmoduleIds).length) { - adUnits.forEach(adUnit => { + adUnits.forEach(adUnit => { + if (adUnit.bids && utils.isArray(adUnit.bids)) { adUnit.bids.forEach(bid => { - // create a User ID object on the bid, - bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = combinedSubmoduleIdsAsEids; + const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); + if (Object.keys(combinedSubmoduleIds).length) { + // create a User ID object on the bid, + bid.userId = combinedSubmoduleIds; + bid.userIdAsEids = createEidsArray(combinedSubmoduleIds); + } }); - }); - } + } + }); } /** - * This is a common function that will initalize subModules if not already done and it will also execute subModule callbacks + * This is a common function that will initialize subModules if not already done and it will also execute subModule callbacks */ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { let delayed = false; @@ -311,6 +498,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { if (typeof initializedSubmodules === 'undefined') { initializedSubmodules = initSubmodules(submodules, gdprDataHandler.getConsentData()); if (initializedSubmodules.length) { + setPrebidServerEidPermissions(initializedSubmodules); // list of submodules that have callbacks that need to be executed const submodulesWithCallbacks = initializedSubmodules.filter(item => utils.isFn(item.callback)); @@ -319,7 +507,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // delay auction until ids are available delayed = true; let continued = false; - const continueCallback = function() { + const continueCallback = function () { if (!continued) { continued = true; continueAuction(); @@ -336,7 +524,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout if (syncDelay > 0) { - setTimeout(function() { + setTimeout(function () { processSubmoduleCallbacks(submodulesWithCallbacks); }, syncDelay); } else { @@ -364,7 +552,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { */ export function requestBidsHook(fn, reqBidsConfigObj) { // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(function() { + initializeSubmodulesAndExecuteCallbacks(function () { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); // calling fn allows prebid to continue processing @@ -392,13 +580,118 @@ function getUserIdsAsEids() { return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); } +/** +* This function will be exposed in the global-name-space so that userIds can be refreshed after initialization. +* @param {RefreshUserIdsOptions} options +*/ +function refreshUserIds(options, callback) { + let submoduleNames = options ? options.submoduleNames : null; + if (!submoduleNames) { + submoduleNames = []; + } + + initializeSubmodulesAndExecuteCallbacks(function() { + let consentData = gdprDataHandler.getConsentData() + + // gdpr consent with purpose one is required, otherwise exit immediately + let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + if (!hasValidated && !hasGDPRConsent(consentData)) { + utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); + return; + } + + // we always want the latest consentData stored, even if we don't execute any submodules + const storedConsentData = getStoredConsentData(); + setStoredConsentData(consentData); + + let callbackSubmodules = []; + for (let submodule of userIdModules) { + if (submoduleNames.length > 0 && + submoduleNames.indexOf(submodule.submodule.name) === -1) { + continue; + } + + utils.logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); + populateSubmoduleId(submodule, consentData, storedConsentData, true); + updateInitializedSubmodules(submodule); + + if (initializedSubmodules.length) { + setPrebidServerEidPermissions(initializedSubmodules); + } + + if (utils.isFn(submodule.callback)) { + callbackSubmodules.push(submodule); + } + } + + if (callbackSubmodules.length > 0) { + processSubmoduleCallbacks(callbackSubmodules); + } + + if (callback) { + callback(); + } + }); +} + /** * This hook returns updated list of submodules which are allowed to do get user id based on TCF 2 enforcement rules configured */ export const validateGdprEnforcement = hook('sync', function (submodules, consentData) { - return {userIdModules: submodules, hasValidated: consentData && consentData.hasValidated}; + return { userIdModules: submodules, hasValidated: consentData && consentData.hasValidated }; }, 'validateGdprEnforcement'); +function populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh) { + // There are two submodule configuration types to handle: storage or value + // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method + // 2. value: pass directly to bids + if (submodule.config.storage) { + let storedId = getStoredValue(submodule.config.storage); + let response; + + let refreshNeeded = false; + if (typeof submodule.config.storage.refreshInSeconds === 'number') { + const storedDate = new Date(getStoredValue(submodule.config.storage, 'last')); + refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); + } + + if (!storedId || refreshNeeded || forceRefresh || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. + response = submodule.submodule.getId(submodule.config, consentData, storedId); + } else if (typeof submodule.submodule.extendId === 'function') { + // If the id exists already, give submodule a chance to decide additional actions that need to be taken + response = submodule.submodule.extendId(submodule.config, consentData, storedId); + } + + if (utils.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); + storedId = response.id; + } + + if (typeof response.callback === 'function') { + // Save async callback to be invoked after auction + submodule.callback = response.callback; + } + } + + if (storedId) { + // cache decoded value (this is copied to every adUnit bid) + submodule.idObj = submodule.submodule.decode(storedId, submodule.config); + } + } else if (submodule.config.value) { + // cache decoded value (this is copied to every adUnit bid) + submodule.idObj = submodule.config.value; + } else { + const response = submodule.submodule.getId(submodule.config, consentData, undefined); + if (utils.isPlainObject(response)) { + if (typeof response.callback === 'function') { submodule.callback = response.callback; } + if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } + } + } +} + /** * @param {SubmoduleContainer[]} submodules * @param {ConsentData} consentData @@ -406,66 +699,38 @@ export const validateGdprEnforcement = hook('sync', function (submodules, consen */ function initSubmodules(submodules, consentData) { // gdpr consent with purpose one is required, otherwise exit immediately - let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + 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`); return []; } - return userIdModules.reduce((carry, submodule) => { - // There are two submodule configuration types to handle: storage or value - // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method - // 2. value: pass directly to bids - if (submodule.config.storage) { - let storedId = getStoredValue(submodule.config.storage); - let response; - - let refreshNeeded = false; - if (typeof submodule.config.storage.refreshInSeconds === 'number') { - const storedDate = new Date(getStoredValue(submodule.config.storage, 'last')); - refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); - } - - if (!storedId || refreshNeeded) { - // No previously saved id. Request one from submodule. - response = submodule.submodule.getId(submodule.config.params, consentData, storedId); - } else if (typeof submodule.submodule.extendId === 'function') { - // If the id exists already, give submodule a chance to decide additional actions that need to be taken - response = submodule.submodule.extendId(submodule.config.params, storedId); - } - - if (utils.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.config.storage, response.id); - storedId = response.id; - } - - if (typeof response.callback === 'function') { - // Save async callback to be invoked after auction - submodule.callback = response.callback; - } - } + // we always want the latest consentData stored, even if we don't execute any submodules + const storedConsentData = getStoredConsentData(); + setStoredConsentData(consentData); - if (storedId) { - // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(storedId, submodule.config.params); - } - } else if (submodule.config.value) { - // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.config.value; - } else { - const response = submodule.submodule.getId(submodule.config.params, consentData, undefined); - if (utils.isPlainObject(response)) { - if (typeof response.callback === 'function') { submodule.callback = response.callback; } - if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config.params); } - } - } + return userIdModules.reduce((carry, submodule) => { + populateSubmoduleId(submodule, consentData, storedConsentData, false); carry.push(submodule); return carry; }, []); } +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 @@ -513,7 +778,10 @@ 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 === i.name); + 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 ? { submodule: i, config: submoduleConfig, @@ -525,7 +793,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`); + utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } } @@ -559,19 +827,19 @@ export function init(config) { ].filter(i => i !== null); // exit immediately if opt out cookie or local storage keys exists. - if (validStorageTypes.indexOf(COOKIE) !== -1 && (coreStorage.getCookie('_pbjs_id_optout') || coreStorage.getCookie('_pubcid_optout'))) { + if (validStorageTypes.indexOf(COOKIE) !== -1 && coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { utils.logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); return; } - // _pubcid_optout is checked for compatiblility with pubCommonId - if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (coreStorage.getDataFromLocalStorage('_pbjs_id_optout') || coreStorage.getDataFromLocalStorage('_pubcid_optout'))) { + if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } + // listen for config userSyncs to be set config.getConfig(conf => { - // Note: support for both 'userSync' and 'usersync' will be deprecated with Prebid.js 3.0 - const userSync = conf.userSync || conf.usersync; + // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 + const userSync = conf.userSync; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; syncDelay = utils.isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; @@ -583,6 +851,7 @@ export function init(config) { // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. (getGlobal()).getUserIds = getUserIds; (getGlobal()).getUserIdsAsEids = getUserIdsAsEids; + (getGlobal()).refreshUserIds = refreshUserIds; } // init config update listener to start the application diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 566d719c90d..88460093c63 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -11,6 +11,16 @@ pbjs.setConfig({ name: "_pubcid", expires: 60 } + }, { + name: 'dmdId', + storage: { + name: 'dmd-dgid', + type: 'cookie', + expires: 30 + }, + params: { + api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by DMD + } }, { name: "unifiedId", params: { @@ -25,13 +35,13 @@ 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://console.id5.io/docs/public/prebid for details + 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://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5 for details }, storage: { - type: "cookie", - name: "id5id.1st", - expires: 90, // Expiration of cookies in days + type: "html5", // ID5 requires html5 + name: "id5id", + expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, }, { @@ -40,10 +50,21 @@ 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 + pid: '999', // 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 property is undefined and 3p request will be fired. }, storage: { type: 'cookie', @@ -53,7 +74,7 @@ pbjs.setConfig({ }, { name: 'liveIntentId', params: { - publisherId: '7798696' // Set an identifier of a publisher know to your systems + publisherId: '7798696' // Set an identifier of a publisher know to your systems }, storage: { type: 'cookie', @@ -61,16 +82,64 @@ pbjs.setConfig({ expires: 60 } }, { - name: 'sharedId', - params: { - syncTime: 60 // in seconds, default is 24 hours - }, - storage: { + name: 'criteo', + storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible type: 'cookie', - name: 'sharedid', - expires: 28 + name: '_criteoId', + expires: 1 } - }], + }, { + name: 'mwOpenLinkId', + params: { + accountId: 0000, + partnerId: 0000, + uid: '12345xyz' + } + },{ + name: "merkleId", + params: { + vendor:'sdfg', + sv_cid:'dfg', + sv_pubid:'xcv', + 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' + } + }, { + name: 'admixerId', + params: { + pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id + e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256) + p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256) + }, + storage: { + type: 'cookie', + name: '__adm__admixer', + expires: 30 + } + },{ + name: 'flocId', + params: { + 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 } @@ -80,7 +149,7 @@ pbjs.setConfig({ Example showing `localStorage` for user id data for some submodules ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "unifiedId", params: { @@ -102,7 +171,8 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999', // 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 property is undefined and 3p request will be fired. }, storage: { type: 'html5', @@ -110,25 +180,111 @@ pbjs.setConfig({ expires: 30 } }, { - name: 'liveIntentId', - params: { - publisherId: '7798696' // Set an identifier of a publisher know to your systems - }, - storage: { - type: 'html5', - name: '_li_pbid', - expires: 60 - } + name: 'liveIntentId', + params: { + publisherId: '7798696' // Set an identifier of a publisher know to your systems + }, + storage: { + type: 'html5', + name: '_li_pbid', + expires: 60 + } }, { - name: 'sharedId', + name: 'sharedId', params: { syncTime: 60 // in seconds, default is 24 hours }, storage: { - type: 'cookie', + type: 'html5', name: 'sharedid', expires: 28 } + }, { + 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://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5 for details + }, + storage: { + type: 'html5', + name: 'id5id', + expires: 90, // Expiration in days + refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' + }, + }, { + name: 'nextrollId', + params: { + partnerId: "1009", // Set your real NextRoll partner ID here for production + } + }, { + name: 'criteo', + storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible + type: 'html5', + name: '_criteoId', + expires: 1 + } + },{ + name: "merkleId", + params: { + vendor:'sdfg', + sv_cid:'dfg', + sv_pubid:'xcv', + 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: { + pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id + e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256) + p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256) + }, + storage: { + type: 'html5', + name: 'admixerId', + expires: 30 + } + },{ + name: 'flocId', + params: { + token: "Registered token or default sharedid.org token" // Default sharedid.org token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" + } + },{ + name: "deepintentId", + storage: { + type: "html5", + name: "_dpes_id", + expires: 90 + } + },{ + name: "deepintentId", + storage: { + type: "cookie", + 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 + } }], syncDelay: 5000 } @@ -138,7 +294,7 @@ pbjs.setConfig({ Example showing how to configure a `value` object to pass directly to bid adapters ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "pubCommonId", value: { @@ -152,6 +308,17 @@ pbjs.setConfig({ { name: "netId", value: { "netId": "fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg" } + }, + { + name: "criteo", + value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } + }, + { + name: "novatiq", + value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } + }, + { + name: 'naveggId', }], syncDelay: 5000 } diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js index 3ed8b2a14b5..e15c9ddaca2 100644 --- a/modules/userIdTargeting.js +++ b/modules/userIdTargeting.js @@ -20,14 +20,16 @@ export function userIdTargeting(userIds, config) { if (!SHARE_WITH_GAM) { logInfo(MODULE_NAME + ': Not enabled for ' + GAM); - } - - if (window.googletag && isFn(window.googletag.pubads) && hasOwn(window.googletag.pubads(), 'setTargeting') && isFn(window.googletag.pubads().setTargeting)) { + } else if (window.googletag && isFn(window.googletag.pubads) && hasOwn(window.googletag.pubads(), 'setTargeting') && isFn(window.googletag.pubads().setTargeting)) { GAM_API = window.googletag.pubads().setTargeting; } else { - SHARE_WITH_GAM = false; - logInfo(MODULE_NAME + ': Could not find googletag.pubads().setTargeting API. Not adding User Ids in targeting.') - return; + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + GAM_API = function (key, value) { + window.googletag.cmd.push(function () { + window.googletag.pubads().setTargeting(key, value); + }); + }; } Object.keys(userIds).forEach(function(key) { diff --git a/modules/userIdTargeting.md b/modules/userIdTargeting.md index f99fd5308b3..340c1b6abf2 100644 --- a/modules/userIdTargeting.md +++ b/modules/userIdTargeting.md @@ -8,7 +8,7 @@ pbjs.setConfig({ // your existing userIds config - usersync: { + userSync: { userIds: [{...}, ...] }, 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..a1d1a3823f0 --- /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 * as utils 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' && !utils.isEmpty(data)) { + check = false; + } else if (typeof data !== 'object' && (utils.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; + utils.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; + } + + utils.logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: expected type ${child.type}`); + }).filter((index, i) => { + let requiredCheck = true; + let mapping = utils.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 = utils.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) utils.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 = utils.deepAccess(ORTB_MAP, path + key); + + if (!mapping || !mapping.invalid) return key; + + utils.logWarn(`Filtered ${parent}${key} property in ortb2 data: invalid property`); + }).filter(key => { + let mapping = utils.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; + + utils.logWarn(`Filtered ${parent}${key} property in ortb2 data: expected type ${(mapping.isArray) ? 'array' : mapping.type}`); + }).reduce((result, key) => { + let mapping = utils.deepAccess(ORTB_MAP, path + key); + let modified = {}; + + if (mapping) { + if (mapping.optoutApplies && optout) { + utils.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 + : utils.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 395953fb737..4bf5a27c002 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,14 +1,14 @@ 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'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; -const BIDDER_CODE = 'vdo.ai'; +const BIDDER_CODE = 'vdoai'; const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -30,17 +30,16 @@ export const spec = { if (validBidRequests.length === 0) { return []; } + return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; - const width = sizes.split('x')[0]; - const height = sizes.split('x')[1]; + const sizes = utils.getAdUnitSizes(bidRequest); const payload = { placementId: bidRequest.params.placementId, - width: width, - height: height, + sizes: sizes, bidId: bidRequest.bidId, referer: bidderRequest.refererInfo.referer, - id: bidRequest.auctionId + id: bidRequest.auctionId, + mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' }; bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); return { @@ -63,9 +62,9 @@ export const spec = { const response = serverResponse.body; const creativeId = response.adid || 0; // const width = response.w || 0; - const width = bidRequest.data.width; + const width = response.width; // const height = response.h || 0; - const height = bidRequest.data.height; + const height = response.height; const cpm = response.price || 0; response.rWidth = width; @@ -90,10 +89,23 @@ export const spec = { ttl: config.getConfig('_bidderTimeout'), // referrer: referrer, // ad: response.adm - ad: adCreative + // ad: adCreative, + mediaType: response.mediaType }; + + if (response.mediaType == 'video') { + bidResponse.vastXml = adCreative; + } else { + bidResponse.ad = adCreative; + } + if (response.adDomain) { + bidResponse.meta = { + advertiserDomains: response.adDomain + } + } bidResponses.push(bidResponse); } + return bidResponses; }, diff --git a/modules/vdoaiBidAdapter.md b/modules/vdoaiBidAdapter.md index 81bd8e69c1d..04200cc9be0 100644 --- a/modules/vdoaiBidAdapter.md +++ b/modules/vdoaiBidAdapter.md @@ -22,13 +22,36 @@ Module that connects to VDO.AI's demand sources }, bids: [ { - bidder: "vdo.ai", + bidder: "vdoai", params: { - placement: 'newsdv77', + placementId: 'newsdv77', bidFloor: 0.01 // Optional } } ] } ]; +``` + + +# Video Test Parameters +``` +var videoAdUnit = { + code: 'test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [ + { + bidder: "vdoai", + params: { + placementId: 'newsdv77' + } + } + ] +}; ``` \ No newline at end of file diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js new file mode 100644 index 00000000000..ca395087d2d --- /dev/null +++ b/modules/verizonMediaIdSystem.js @@ -0,0 +1,105 @@ +/** + * This module adds verizonMediaId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/verizonMediaIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const MODULE_NAME = 'verizonMediaId'; +const VENDOR_ID = 25; +const PLACEHOLDER = '__PIXEL_ID__'; +const VMCID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; + +function isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); +} + +/** @type {Submodule} */ +export const verizonMediaIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Vendor id of Verizon Media EMEA Limited + * @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 || value.vmuid)) + ? {connectid: value.connectid || value.vmuid} : undefined; + }, + /** + * Gets the Verizon Media Connect ID + * @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')) { + utils.logError('The verizonMediaId 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) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.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)}`; + verizonMediaIdSubmodule.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', verizonMediaIdSubmodule); diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md new file mode 100644 index 00000000000..c0d315dc754 --- /dev/null +++ b/modules/verizonMediaSystemId.md @@ -0,0 +1,33 @@ +## Verizon Media User ID Submodule + +Verizon Media User ID Module. + +### Prebid Params + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'verizonMediaId', + storage: { + name: 'vmcid', + type: 'html5', + expires: 15 + }, + params: { + pixelId: 58776, + he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Verizon Media User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Verizon Media module - `"verizonMediaId"` | `"verizonMediaId"` | +| params | Required | Object | Data for Verizon Media ID initialization. | | +| params.pixelId | Required | Number | The Verizon Media supplied publisher specific pixel Id | `8976` | +| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | 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/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 3eea270dc8d..3fb94870d3f 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,20 +1,17 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; const BIDDER_VERSION = '1.0.0'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; -const INTERNAL_SYNC_TYPE = { - IFRAME: 'iframe', - IMAGE: 'img' -}; -const EXTERNAL_SYNC_TYPE = { - IFRAME: 'iframe', - IMAGE: 'image' -}; +const DEAL_ID_EXPIRY = 1000 * 60 * 15; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const SESSION_ID_KEY = 'vidSid'; export const SUPPORTED_ID_SYSTEMS = { 'britepoolid': 1, 'criteoId': 1, @@ -23,25 +20,43 @@ export const SUPPORTED_ID_SYSTEMS = { 'idl_env': 1, 'lipb': 1, 'netId': 1, - 'parrableid': 1, + 'parrableId': 1, 'pubcid': 1, 'tdid': 1, }; +const storage = getStorageManager(GVLID); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; } +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + function isBidRequestValid(bid) { const params = bid.params || {}; - return !!(params.cId && params.pId); + return !!(extractCID(params) && extractPID(params)); } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { const { params, bidId, userId, adUnitCode } = bid; - const { bidFloor, cId, pId, ext, subDomain } = params; + const { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const sId = getVidazooSessionId(); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); let data = { url: encodeURIComponent(topWindowUrl), @@ -50,8 +65,10 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { bidId: bidId, adUnitCode: adUnitCode, publisherId: pId, + sessionId: sId, sizes: sizes, dealId: dealId, + uniqueDealId: uniqueDealId, bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}` @@ -70,6 +87,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { if (bidderRequest.uspConsent) { data.usPrivacy = bidderRequest.uspConsent } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -96,6 +114,12 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { case 'lipb': payloadRef[key] = userId.lipbid; break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; default: payloadRef[key] = userId; } @@ -125,7 +149,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; } @@ -138,7 +162,10 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - ad: ad + ad: ad, + meta: { + advertiserDomains: advertiserDomains || [] + } }) }); return output; @@ -147,42 +174,27 @@ function interpretResponse(serverResponse, request) { } } -function getUserSyncs(syncOptions, responses) { +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; const { iframeEnabled, pixelEnabled } = syncOptions; - + const { gdprApplies, consentString = '' } = gdprConsent; + const params = `?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` if (iframeEnabled) { - return [{ + syncs.push({ type: 'iframe', - url: 'https://static.cootlogix.com/basev/sync/user_sync.html' - }]; + url: `https://prebid.cootlogix.com/api/sync/iframe/${params}` + }); } - if (pixelEnabled) { - const lookup = {}; - const syncs = []; - responses.forEach(response => { - const { body } = response; - const results = body ? body.results || [] : []; - results.forEach(result => { - (result.cookies || []).forEach(cookie => { - if (cookie.type === INTERNAL_SYNC_TYPE.IMAGE) { - if (pixelEnabled && !lookup[cookie.src]) { - syncs.push({ - type: EXTERNAL_SYNC_TYPE.IMAGE, - url: cookie.src - }); - } - } - }); - }); + syncs.push({ + type: 'image', + url: `https://prebid.cootlogix.com/api/sync/image/${params}` }); - return syncs; } - - return []; + return syncs; } -function hashCode(s, prefix = '_') { +export function hashCode(s, prefix = '_') { const l = s.length; let h = 0 let i = 0; @@ -192,38 +204,73 @@ function hashCode(s, prefix = '_') { return prefix + h; } -function getNextDealId(key) { +export function getNextDealId(key, expiry = DEAL_ID_EXPIRY) { try { - const currentValue = Number(getStorageItem(key) || 0); + const data = getStorageItem(key); + let currentValue = 0; + let timestamp; + + if (data && data.value && Date.now() - data.created < expiry) { + currentValue = data.value; + timestamp = data.created; + } + const nextValue = currentValue + 1; - setStorageItem(key, nextValue); + setStorageItem(key, nextValue, timestamp); return nextValue; } catch (e) { return 0; } } -function getStorage() { - return window['sessionStorage']; +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getVidazooSessionId() { + return getStorageItem(SESSION_ID_KEY) || ''; } -function getStorageItem(key) { +export function getStorageItem(key) { try { - return getStorage().getItem(key); - } catch (e) { - return null; - } + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { } + + return null; } -function setStorageItem(key, value) { +export function setStorageItem(key, value, timestamp) { try { - getStorage().setItem(key, String(value)); + const created = timestamp || Date.now(); + const data = JSON.stringify({ value, created }); + storage.setDataInLocalStorage(key, data); } catch (e) { } } +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + export const spec = { code: BIDDER_CODE, 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..076329d1c8b --- /dev/null +++ b/modules/videobyteBidAdapter.js @@ -0,0 +1,311 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.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/ortb/', + + /** + * 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; + if (bidRequest.params.video && bidRequest.params.video.e2etest) { + utils.logMessage('E2E test mode enabled'); + pubId = 'e2etest' + } + return { + method: 'POST', + url: spec.ENDPOINT + pubId, + 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) + } + } else { + utils.logError('invalid server response received'); + } + 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 = 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 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 = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.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 (utils.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 && utils.isPlainObject(videoParams.content)) { + openrtbRequest.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + const contentObjectKeys = ['ext']; + for (const contentKey in videoBidderParams.content) { + if ( + (contentStringKeys.indexOf(contentKey) > -1 && utils.isStr(videoParams.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && utils.isNumber(videoParams.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && utils.isPlainObject(videoParams.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && utils.isArray(videoParams.content[contentKey]) && + videoParams.content[contentKey].every(catStr => utils.isStr(catStr)))) { + openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; + } else { + utils.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) { + utils.deepSetValue(openrtbRequest, 'source.ext.schain', bidRequest.schain); + openrtbRequest.source.ext.schain.nodes[0].rid = openrtbRequest.id; + } + + // 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)); + } + + // CCPA + if (bidderRequest.uspConsent) { + utils.deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + return openrtbRequest; +} + +function validateVideo(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (!bidRequest.params.pubId) { + utils.logError('failed validation: publisher id not declared'); + return false; + } + + const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + + if (videoBidderParams && videoBidderParams.e2etest) { + return true; + } + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (!videoParams.context) { + utils.logError('failed validation: context id not declared'); + return false; + } + if (videoParams.context !== 'instream') { + utils.logError('failed validation: only context instream is supported '); + return false; + } + + if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { + utils.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..fc2b0bce4b5 --- /dev/null +++ b/modules/videobyteBidAdapter.md @@ -0,0 +1,78 @@ +# 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' + } + } + ] + } + ] +``` + +# 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..c307fc3e865 100644 --- a/modules/videoreachBidAdapter.js +++ b/modules/videoreachBidAdapter.js @@ -2,9 +2,11 @@ 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) { @@ -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..b579de8618b --- /dev/null +++ b/modules/vidoomyBidAdapter.js @@ -0,0 +1,200 @@ +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 { 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 isBidRequestValid = bid => { + if (!bid.params) { + utils.logError(BIDDER_CODE + ': bid.params should be non-empty'); + return false; + } + + if (!+bid.params.pid) { + utils.logError(BIDDER_CODE + ': bid.params.pid should be non-empty Number'); + return false; + } + + if (!+bid.params.id) { + utils.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) { + utils.logError(BIDDER_CODE + ': bid.params.mediaType.video should have a playerSize property to tell player size when is INSTREAM'); + return false; + } + + return true; +}; + +const isBidResponseValid = bid => { + if (!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); + default: + return false; + } +} + +const buildRequests = (validBidRequests, bidderRequest) => { + const serverRequests = validBidRequests.map(bid => { + let adType = BANNER; + let w, h; + if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + [w, h] = bid.mediaTypes[BANNER].sizes[0]; + adType = BANNER; + } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + [w, h] = bid.mediaTypes[VIDEO].playerSize; + adType = VIDEO; + } + + let host = ''; + try { + host = bidderRequest.refererInfo.referer.split('#')[0].replace(/^(https\:\/\/|http\:\/\/)|(\/)$/g, '').split('/')[0]; + } catch (eBidRequest) { + try { + host = window.location.href.replace(/^(https\:\/\/|http\:\/\/)|(\/)$/g, '').split('/')[0]; + } catch (eLocationHref) { + host = window.location.href; + } + } + const hostname = host.split(':')[0]; + + const videoContext = utils.deepAccess(bid, 'mediaTypes.video.context'); + + const queryParams = []; + queryParams.push(['id', bid.params.id]); + queryParams.push(['adtype', adType]); + queryParams.push(['w', w]); + queryParams.push(['h', h]); + queryParams.push(['pos', parseInt(bid.params.position) || 1]); + queryParams.push(['ua', navigator.userAgent]); + queryParams.push(['l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '']); + queryParams.push(['dt', /Mobi/.test(navigator.userAgent) ? 2 : 1]); + queryParams.push(['pid', bid.params.pid]); + queryParams.push(['requestId', bid.bidId]); + queryParams.push(['d', hostname]); + queryParams.push(['sp', encodeURIComponent(bidderRequest.refererInfo.referer)]); + if (bidderRequest.gdprConsent) { + queryParams.push(['gdpr', bidderRequest.gdprConsent.gdprApplies]); + queryParams.push(['gdprcs', bidderRequest.gdprConsent.consentString]); + } + queryParams.push(['usp', bidderRequest.uspConsent || '']); + queryParams.push(['coppa', !!config.getConfig('coppa')]); + + const rawQueryParams = queryParams.map(qp => qp.join('=')).join('&'); + + const url = `${ENDPOINT}?${rawQueryParams}`; + return { + method: 'GET', + url: url, + data: {videoContext} + } + }); + 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.mediaType === 'video') { + responseBody.ad = responseBody.vastUrl; + 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; + utils.logError(BIDDER_CODE + ': error while installing renderer to show outstream ad'); + } + } + } + const bid = { + vastUrl: responseBody.vastUrl, + 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 + } + }; + + const bids = []; + + if (isBidResponseValid(bid)) { + bids.push(bid); + } else { + utils.logError(BIDDER_CODE + ': server returns invalid response'); + } + + return bids; + } catch (e) { + utils.logError(BIDDER_CODE + ': error parsing server response to Prebid format'); + return []; + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + gvlid: GVLID, +}; + +registerBidder(spec); diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md new file mode 100644 index 00000000000..11e0ad40dbb --- /dev/null +++ b/modules/vidoomyBidAdapter.md @@ -0,0 +1,59 @@ +# 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' + } + } + ] + } +]; +``` + +For video +```js +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ + { + bidder: 'vidoomy', + params: { + id: '123123', + pid: '123123' + } + } + ] + } +]; +``` diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index 52894ef2dd9..212759642f5 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -13,6 +13,7 @@ const DISPLAY = 'display'; export const spec = { code: BIDDER_CODE, aliases: ['viewdeos'], + gvlid: 924, supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { return !!utils.deepAccess(bid, 'params.aid'); @@ -189,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 511e658c947..fb9b21dc167 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,15 +1,23 @@ import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.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, logError, deepAccess } = utils; const BIDDER_CODE = 'visx'; -const ENDPOINT_URL = 'https://t.visx.net/hb'; +const GVLID = 154; +const BASE_URL = 'https://t.visx.net'; +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 = 'https://t.visx.net/push_sync'; +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', @@ -17,98 +25,76 @@ const LOG_ERROR_MESS = { hasEmptySeatbidArray: 'Response has empty seatbid array', hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ', notAllowedCurrency: 'Currency is not supported - ', - currencyMismatch: 'Currency from the request is not match currency from the response - ' + currencyMismatch: 'Currency from the request is not match currency from the response - ', + onlyVideoInstream: `Only video ${VIDEO_INSTREAM} supported`, + videoMissing: 'Bid request videoType property is missing - ' }; const currencyWhiteList = ['EUR', 'USD', 'GBP', 'PLN']; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { + if (_isVideoBid(bid)) { + 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; }, 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; + let payloadUserEids; + let timeout; if (currencyWhiteList.indexOf(currency) === -1) { - utils.logError(LOG_ERROR_MESS.notAllowedCurrency + currency); + 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 = 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); + 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: utils.getKeys(sizeMap).join(','), - r: reqId, - cur: currency, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (payloadSchain) { - payload.schain = JSON.stringify(payloadSchain); - } - - if (payloadUserId) { - if (payloadUserId.tdid) { - payload.tdid = payloadUserId.tdid; - } - if (payloadUserId.id5id) { - payload.id5 = payloadUserId.id5id; - } - 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; } @@ -122,19 +108,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; @@ -145,13 +162,10 @@ 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) utils.logError(errorMessage); + if (errorMessage) logError(errorMessage); return bidResponses; }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { @@ -167,78 +181,186 @@ export const spec = { } 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 + if (bid.ext && bid.ext.events && bid.ext.events.pending) { + utils.triggerPixel(bid.ext.events.pending); + } + }, + onBidWon: function(bid) { + // Call '/track/win' with the corresponding bid.requestId + if (bid.ext && bid.ext.events && bid.ext.events.win) { + utils.triggerPixel(bid.ext.events.win); + } + }, + onTimeout: function(timeoutData) { + // Call '/track/bid_timeout' with timeout data + utils.triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '?data=' + 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 = utils.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: utils.deepAccess(videoParams, 'playerSize.0.0'), h: utils.deepAccess(videoParams, 'playerSize.0.1') }); + + if (video.w && video.h && video.mimes) { + return video; + } +} + +function buildImpObject(bid) { + const { params: { uid }, bidId, mediaTypes, sizes } = 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.ext.bidder.uid && (impObject.banner || impObject.video)) { + return impObject; + } +} + 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]; } -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(); - bidResponses.push({ - requestId: bid.bidId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, - currency: reqCurrency, - netRevenue: true, - ttl: TIME_TO_LIVE, - ad: serverBid.adm, - dealId: serverBid.dealid - }); - - 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]; - } - } - }); - } + 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; } } if (errorMessage) { - utils.logError(errorMessage); + logError(errorMessage); + } +} + +function _isVideoBid(bid) { + return bid.mediaType === VIDEO || deepAccess(bid, 'mediaTypes.video'); +} + +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 (!_isVideoInstreamBid(bid)) { + if (logErrors) { + logError(LOG_ERROR_MESS.onlyVideoInstream); + } + result = false; + } + if (!(videoMediaType.playerSize && parseSizesInput(deepAccess(videoMediaType, 'playerSize', [])))) { + if (logErrors) { + logError(LOG_ERROR_MESS.videoMissing + 'playerSize'); + } + result = false; + } + if (!videoMediaType.mimes) { + if (logErrors) { + logError(LOG_ERROR_MESS.videoMissing + 'mimes'); + } + result = false; + } + if (!videoMediaType.protocols) { + if (logErrors) { + logError(LOG_ERROR_MESS.videoMissing + 'protocols'); + } + result = false; } + return result; } registerBidder(spec); diff --git a/modules/visxBidAdapter.md b/modules/visxBidAdapter.md index 41e45622481..9578f7cc4a7 100644 --- a/modules/visxBidAdapter.md +++ b/modules/visxBidAdapter.md @@ -11,33 +11,61 @@ Maintainer: service@yoc.com Module that connects to YOC VIS.X® demand source to fetch bids. # Test Parameters +```javascript +var adUnits = [ + // YOC Mystery Ad adUnit + { + code: 'yma-test-div', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bids: [ + { + bidder: 'visx', + params: { + uid: '903535' + } + } + ] + }, + // YOC Understitial Ad adUnit + { + code: 'yua-test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'visx', + params: { + uid: '903536' + } + } + ] + }, + // YOC In-stream adUnit + { + code: 'instream-test-div', + mediaTypes: { + video: { + context: 'instream', + playerSize: [400, 300], + mimes: ['video/mp4'], + protocols: [3, 6] + }, + }, + bids: [ + { + bidder: 'visx', + params: { + uid: '921068' + } + } + ] + } +]; ``` - var adUnits = [ - // YOC Mystery Ad adUnit - { - code: 'yma-test-div', - sizes: [[1, 1]], - bids: [ - { - bidder: 'visx', - params: { - uid: '903535' - } - } - ] - }, - // YOC Understitial Ad adUnit - { - code: 'yua-test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'visx', - params: { - uid: '903536' - } - } - ] - } - ]; -``` \ No newline at end of file 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 new file mode 100644 index 00000000000..ba469c3c7ed --- /dev/null +++ b/modules/voxBidAdapter.js @@ -0,0 +1,250 @@ +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 {auctionManager} from '../src/auctionManager.js'; +import {Renderer} from '../src/Renderer.js'; + +const BIDDER_CODE = 'vox'; +const SSP_ENDPOINT = 'https://ssp.hybrid.ai/auction/prebid'; +const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const TTL = 60; + +function buildBidRequests(validBidRequests) { + return utils._map(validBidRequests, function(validBidRequest) { + const params = validBidRequest.params; + const bidRequest = { + bidId: validBidRequest.bidId, + transactionId: validBidRequest.transactionId, + sizes: validBidRequest.sizes, + placement: params.placement, + placeId: params.placementId, + imageUrl: params.imageUrl + }; + + return bidRequest; + }) +} + +const outstreamRender = bid => { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bid.vastXml + } + }); + }); +} + +const createRenderer = (bid) => { + const renderer = Renderer.install({ + targetId: bid.adUnitCode, + url: VIDEO_RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +function buildBid(bidData) { + const bid = { + requestId: bidData.bidId, + cpm: bidData.price, + width: bidData.content.width, + height: bidData.content.height, + creativeId: bidData.content.seanceId || bidData.bidId, + currency: bidData.currency, + netRevenue: true, + mediaType: BANNER, + ttl: TTL, + content: bidData.content, + meta: { + advertiserDomains: bidData.advertiserDomains || [], + } + }; + + if (bidData.placement === 'video') { + bid.vastXml = bidData.content; + bid.mediaType = VIDEO; + + let adUnit = find(auctionManager.getAdUnits(), function (unit) { + return unit.transactionId === bidData.transactionId; + }); + + if (adUnit) { + bid.width = adUnit.mediaTypes.video.playerSize[0][0]; + bid.height = adUnit.mediaTypes.video.playerSize[0][1]; + + if (adUnit.mediaTypes.video.context === 'outstream') { + bid.renderer = createRenderer(bid); + } + } + } else if (bidData.placement === 'inImage') { + bid.mediaType = BANNER; + bid.ad = wrapInImageBanner(bid, bidData); + } else { + bid.mediaType = BANNER; + bid.ad = wrapBanner(bid, bidData); + } + + return bid; +} + +function getMediaTypeFromBid(bid) { + return bid.mediaTypes && Object.keys(bid.mediaTypes)[0]; +} + +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')); + + return isHasVideoContext && isPlayerSize; +} + +function wrapInImageBanner(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +function wrapBanner(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +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(bid) { + return ( + !!bid.params.placementId && + !!bid.params.placement && + ( + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'inImage' && !!bid.params.imageUrl) || + (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) + ) + ); + }, + + /** + * 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) { + const payload = { + url: bidderRequest.refererInfo.referer, + cmp: !!bidderRequest.gdprConsent, + bidRequests: buildBidRequests(validBidRequests) + }; + + if (payload.cmp) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (gdprApplies !== undefined) payload['ga'] = gdprApplies; + payload['cs'] = bidderRequest.gdprConsent.consentString; + } + + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: SSP_ENDPOINT, + data: payloadString, + options: { + 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) { + 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) { + let rawBid = find(bidRequests, function (item) { + return item.bidId === bid.bidId; + }); + bid.placement = rawBid.placement; + bid.transactionId = rawBid.transactionId; + bid.placeId = rawBid.placeId; + return buildBid(bid); + }); + } else { + return []; + } + } + +} +registerBidder(spec); diff --git a/modules/voxBidAdapter.md b/modules/voxBidAdapter.md new file mode 100644 index 00000000000..3fc0383e6f8 --- /dev/null +++ b/modules/voxBidAdapter.md @@ -0,0 +1,237 @@ +# Overview + + +**Module Name**: VOX Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@hybrid.ai + +# Description + +You can use this adapter to get a bid from partners.hybrid.ai + + +## Sample Banner Ad Unit + +```js +var adUnits = [{ + code: 'banner_ad_unit', + mediaTypes: { + banner: { + sizes: [[160, 600]] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "banner", // required + placementId: "5fc77bc5a757531e24c89a4c" // required + } + }] +}]; +``` + +## Sample Video Ad Unit + +```js +var adUnits = [{ + code: 'video_ad_unit', + mediaTypes: { + video: { + context: 'outstream', // required + playerSize: [[640, 480]] // required + } + }, + bids: [{ + bidder: 'vox', + params: { + placement: "video", // required + placementId: "5fc77a94a757531e24c89a3d" // required + } + }] +}]; +``` + +# Sample In-Image Ad Unit + +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [0, 0] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "inImage", + placementId: "5fc77b40a757531e24c89a42", + imageUrl: "https://gallery.voxexchange.io/vox-main.png" + } + }] +}]; +``` + +# Example page with In-Image + +```html + + + + + Prebid.js Banner Example + + + + + +

Prebid.js InImage Banner Test

+
+ + +
+ + +``` + +# Example page with In-Image and GPT + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ + +
+ + +``` 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 new file mode 100644 index 00000000000..7a83bf4e331 --- /dev/null +++ b/modules/vuukleBidAdapter.js @@ -0,0 +1,67 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'vuukle'; +const URL = 'https://pb.vuukle.com/adapter'; +const TIME_TO_LIVE = 360; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return true + }, + + buildRequests: function(bidRequests) { + const requests = bidRequests.map(function (bid) { + const parseSized = utils.parseSizesInput(bid.sizes); + const arrSize = parseSized[0].split('x'); + const params = { + url: encodeURIComponent(window.location.href), + sizes: JSON.stringify(parseSized), + width: arrSize[0], + height: arrSize[1], + params: JSON.stringify(bid.params), + rnd: Math.random(), + bidId: bid.bidId, + source: 'pbjs', + version: '$prebid.version$', + v: 1, + }; + + return { + method: 'GET', + url: URL, + data: params, + options: {withCredentials: false} + } + }); + + return requests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.ad) { + return []; + } + + const res = serverResponse.body; + const bidResponse = { + requestId: bidRequest.data.bidId, + cpm: res.cpm, + width: res.width, + height: res.height, + creativeId: res.creative_id, + currency: res.currency || 'USD', + netRevenue: true, + ttl: TIME_TO_LIVE, + ad: res.ad, + meta: { + advertiserDomains: Array.isArray(res.adomain) ? res.adomain : [] + } + }; + + return [bidResponse]; + }, +} +registerBidder(spec); diff --git a/modules/vuukleBidAdapter.md b/modules/vuukleBidAdapter.md new file mode 100644 index 00000000000..ee7b54c6262 --- /dev/null +++ b/modules/vuukleBidAdapter.md @@ -0,0 +1,26 @@ +# Overview +``` +Module Name: Vuukle Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@vuukle.com +``` + +# Description +Module that connects to Vuukle's server for bids. +Currently module supports only banner mediaType. + +# Test Parameters +``` + var adUnits = [{ + code: '/test/div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'vuukle', + params: {} + }] + }]; +``` diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 255bf24098b..bc60e151e39 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -1,218 +1,295 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.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 domain = 'hb.justbidit.xyz'; -const httpsPort = 8843; -const path = '/prebid'; +const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`; +const BIDDER_CODE = 'waardex'; -const ENDPOINT = `https://${domain}:${httpsPort}${path}`; +const isBidRequestValid = bid => { + if (!bid.bidId) { + utils.logError(BIDDER_CODE + ': bid.bidId should be non-empty'); + return false; + } -const BIDDER_CODE = 'waardex'; + if (!bid.params) { + utils.logError(BIDDER_CODE + ': bid.params should be non-empty'); + return false; + } -/** - * @param {Array} requestSizes - * - * @returns {Array} - * */ -function transformSizes(requestSizes) { - let sizes = []; - if ( - Array.isArray(requestSizes) && - !Array.isArray(requestSizes[0]) - ) { - sizes[0] = { - width: parseInt(requestSizes[0], 10) || 0, - height: parseInt(requestSizes[1], 10) || 0, - }; - } else if ( - Array.isArray(requestSizes) && - Array.isArray(requestSizes[0]) - ) { - sizes = requestSizes.map(item => { - return { - width: parseInt(item[0], 10) || 0, - height: parseInt(item[1], 10) || 0, - } - }); + if (!+bid.params.zoneId) { + utils.logError(BIDDER_CODE + ': bid.params.zoneId should be non-empty Number'); + return false; } - return sizes; -} -/** - * @param {Object} banner - * @param {Array} banner.sizes - * - * @returns {Object} - * */ -function createBannerObject(banner) { - return { - sizes: transformSizes(banner.sizes), - }; -} - -/** - * @param {Array} validBidRequests - * - * @returns {Object} - * */ -function buildBidRequests(validBidRequests) { - return validBidRequests.map((validBidRequest) => { - const params = validBidRequest.params; - - const item = { - bidId: validBidRequest.bidId, - placementId: params.placementId, - bidfloor: parseFloat(params.bidfloor) || 0, - position: parseInt(params.position) || 1, - instl: parseInt(params.instl) || 0, - }; - if (validBidRequest.mediaTypes[BANNER]) { - item[BANNER] = createBannerObject(validBidRequest.mediaTypes[BANNER]); + if (bid.mediaTypes && bid.mediaTypes.video) { + if (!bid.mediaTypes.video.playerSize) { + utils.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'); + return false; } - return item; - }); -} - -/** - * @param {Object} bidderRequest - * @param {String} bidderRequest.userAgent - * @param {String} bidderRequest.refererInfo - * @param {String} bidderRequest.uspConsent - * @param {Object} bidderRequest.gdprConsent - * @param {String} bidderRequest.gdprConsent.consentString - * @param {String} bidderRequest.gdprConsent.gdprApplies - * - * @returns {Object} - { - * ua: string, - * language: string, - * [referer]: string, - * [us_privacy]: string, - * [consent_string]: string, - * [consent_required]: string, - * [coppa]: boolean, - * } - * */ -function getCommonBidsData(bidderRequest) { + + if (!bid.mediaTypes.video.playerSize[0]) { + utils.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'); + return false; + } + } + + return true; +}; + +const buildRequests = (validBidRequests, bidderRequest) => { + const dataToSend = { + ...getCommonBidsData(bidderRequest), + bidRequests: getBidRequestsToSend(validBidRequests) + }; + + let zoneId = ''; + if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.zoneId) { + zoneId = +validBidRequests[0].params.zoneId; + } + + return {method: 'POST', url: `${ENDPOINT}?pubId=${zoneId}`, data: dataToSend}; +}; + +const getCommonBidsData = bidderRequest => { const payload = { ua: navigator.userAgent || '', language: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', - }; + if (bidderRequest && bidderRequest.refererInfo) { payload.referer = encodeURIComponent(bidderRequest.refererInfo.referer); } + if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = { consent_string: bidderRequest.gdprConsent.consentString, consent_required: bidderRequest.gdprConsent.gdprApplies, } } + payload.coppa = !!config.getConfig('coppa'); return payload; -} - -/** - * this function checks either bid response is valid or noе - * - * @param {Object} bid - * @param {string} bid.requestId - * @param {number} bid.cpm - * @param {string} bid.creativeId - * @param {number} bid.ttl - * @param {string} bid.currency - * @param {number} bid.width - * @param {number} bid.height - * @param {string} bid.ad - * - * @returns {boolean} - * */ -function isBidValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; +}; + +const getBidRequestsToSend = validBidRequests => { + return validBidRequests.map(getBidRequestToSend); +}; + +const getBidRequestToSend = validBidRequest => { + const result = { + bidId: validBidRequest.bidId, + bidfloor: 0, + position: parseInt(validBidRequest.params.position) || 1, + instl: parseInt(validBidRequest.params.instl) || 0, + }; + + if (validBidRequest.mediaTypes[BANNER]) { + result[BANNER] = createBannerObject(validBidRequest.mediaTypes[BANNER]); } - return Boolean(bid.width && bid.height && bid.ad); -} - -/** - * @param {Object} serverBid - * - * @returns {Object|null} - * */ -function createBid(serverBid) { - const bid = { - requestId: serverBid.id, - cpm: serverBid.price, + if (validBidRequest.mediaTypes[VIDEO]) { + result[VIDEO] = createVideoObject(validBidRequest.mediaTypes[VIDEO], validBidRequest.params); + } + + return result; +}; + +const createBannerObject = banner => { + return { + sizes: transformSizes(banner.sizes), + }; +}; + +const transformSizes = requestSizes => { + let result = []; + + if (Array.isArray(requestSizes) && !Array.isArray(requestSizes[0])) { + result[0] = { + width: parseInt(requestSizes[0], 10) || 0, + height: parseInt(requestSizes[1], 10) || 0, + }; + } else if (Array.isArray(requestSizes) && Array.isArray(requestSizes[0])) { + result = requestSizes.map(item => { + return { + width: parseInt(item[0], 10) || 0, + height: parseInt(item[1], 10) || 0, + } + }); + } + + return result; +}; + +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 + }; +}; + +const interpretResponse = (serverResponse, bidRequest) => { + try { + const responseBody = serverResponse.body; + + if (!responseBody.seatbid || !responseBody.seatbid[0]) { + return []; + } + + return responseBody.seatbid[0].bid + .map(openRtbBid => { + const hbRequestBid = getHbRequestBid(openRtbBid, bidRequest.data); + if (!hbRequestBid) return; + + const hbRequestMediaType = getHbRequestMediaType(hbRequestBid); + if (!hbRequestMediaType) return; + + return mapOpenRtbToHbBid(openRtbBid, hbRequestMediaType, hbRequestBid); + }) + .filter(x => x); + } catch (e) { + return []; + } +}; + +const getHbRequestBid = (openRtbBid, bidRequest) => { + return find(bidRequest.bidRequests, x => x.bidId === openRtbBid.impid); +}; + +const getHbRequestMediaType = hbRequestBid => { + if (hbRequestBid.banner) return BANNER; + if (hbRequestBid.video) return VIDEO; + return null; +}; + +const mapOpenRtbToHbBid = (openRtbBid, mediaType, hbRequestBid) => { + let bid = null; + + if (mediaType === BANNER) { + bid = mapOpenRtbBannerToHbBid(openRtbBid, hbRequestBid); + } + + if (mediaType === VIDEO) { + bid = mapOpenRtbVideoToHbBid(openRtbBid, hbRequestBid); + } + + return isBidValid(bid) ? bid : null; +}; + +const mapOpenRtbBannerToHbBid = (openRtbBid, hbRequestBid) => { + return { + mediaType: BANNER, + requestId: hbRequestBid.bidId, + cpm: openRtbBid.price, currency: 'USD', - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.crid, + width: openRtbBid.w, + height: openRtbBid.h, + creativeId: openRtbBid.crid, netRevenue: true, ttl: 3000, - ad: serverBid.adm, - dealId: serverBid.dealid, + ad: openRtbBid.adm, + dealId: openRtbBid.dealid, meta: { - cid: serverBid.cid, - adomain: serverBid.adomain, - mediaType: serverBid.ext.mediaType + cid: openRtbBid.cid, + adomain: openRtbBid.adomain, + mediaType: openRtbBid.ext && openRtbBid.ext.mediaType }, }; +}; - return isBidValid(bid) ? bid : null; -} +const mapOpenRtbVideoToHbBid = (openRtbBid, hbRequestBid) => { + return { + mediaType: VIDEO, + requestId: hbRequestBid.bidId, + cpm: openRtbBid.price, + currency: 'USD', + width: hbRequestBid.video.w, + height: hbRequestBid.video.h, + ad: openRtbBid.adm, + ttl: 3000, + creativeId: openRtbBid.crid, + netRevenue: true, + vastUrl: getVastUrl(openRtbBid), + // An impression tracking URL to serve with video Ad + // Optional; only usable with vastUrl and requires prebid cache to be enabled + // Example: "https://vid.exmpale.com/imp/134" + // For now we don't need this field + // vastImpUrl: null, + vastXml: openRtbBid.adm, + dealId: openRtbBid.dealid, + meta: { + cid: openRtbBid.cid, + adomain: openRtbBid.adomain, + networkId: null, + networkName: null, + agencyId: null, + agencyName: null, + advertiserId: null, + advertiserName: null, + advertiserDomains: null, + brandId: null, + brandName: null, + primaryCatId: null, + secondaryCatIds: null, + mediaType: 'video', + }, + } +}; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => Boolean(bid.bidId && bid.params && +bid.params.placementId && +bid.params.pubId), - - /** - * @param {Object[]} validBidRequests - array of valid bid requests - * @param {Object} bidderRequest - an array of valid bid requests - * - * */ - buildRequests(validBidRequests, bidderRequest) { - const payload = getCommonBidsData(bidderRequest); - payload.bidRequests = buildBidRequests(validBidRequests); - - let pubId = ''; - if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.pubId) { - pubId = +validBidRequests[0].params.pubId; - } +const getVastUrl = openRtbBid => { + const adm = (openRtbBid.adm || '').trim(); - const url = `${ENDPOINT}?pubId=${pubId}`; + if (adm.startsWith('http')) { + return adm; + } else { + return null + } +}; - return { - method: 'POST', - url, - data: payload - }; - }, - - /** - * Unpack the response from the server into a list of bids. - */ - interpretResponse(serverResponse, bidRequest) { - const bids = []; - serverResponse = serverResponse.body; - - if (serverResponse.seatbid && serverResponse.seatbid[0]) { - const oneSeatBid = serverResponse.seatbid[0]; - oneSeatBid.bid.forEach(serverBid => { - const bid = createBid(serverBid); - if (bid) { - bids.push(bid); - } - }); - } - return bids; - }, -} +const isBidValid = bid => { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + return Boolean(bid.width && bid.height && bid.ad); +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, +}; registerBidder(spec); diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md index 44ee720d31a..6978281d40e 100644 --- a/modules/waardexBidAdapter.md +++ b/modules/waardexBidAdapter.md @@ -10,51 +10,82 @@ Maintainer: info@prebid.org Connects to Waardex exchange for bids. -Waardex bid adapter supports Banner. +Waardex bid adapter supports Banner and Video. # Test Parameters - -``` - +## Banner +```javascript var sizes = [ [300, 250] ]; var PREBID_TIMEOUT = 5000; var FAILSAFE_TIMEOUT = 5000; -var adUnits = [{ - code: '/19968336/header-bid-tag-0', - mediaTypes: { - banner: { - sizes: sizes, - }, - }, - bids: [{ - bidder: 'waardex', - params: { - placementId: 13144370, - position: 1, // add position openrtb - bidfloor: 0.5, - instl: 0, // 1 - full screen - pubId: 1, +var adUnits = [ + { + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: sizes + } + }, + bids: [ + { + bidder: 'waardex', + params: { + position: 1, + instl: 1, + zoneId: 1 + } + } + ] } - }] -},{ - code: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: sizes, +]; +``` + +## Video + +```javascript +const PREBID_TIMEOUT = 1000; +const FAILSAFE_TIMEOUT = 3000; + +const sizes = [ + [640, 480] +]; + +const adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + 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: { - placementId: 333333333333, - position: 1, // add position openrtb - bidfloor: 0.5, - instl: 0, // 1 - full screen - pubId: 1, - } - }] -}]; + bids: [ + { + bidder: 'waardex', + params: { + instl: 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/welectBidAdapter.js b/modules/welectBidAdapter.js deleted file mode 100644 index 3913cfde6a0..00000000000 --- a/modules/welectBidAdapter.js +++ /dev/null @@ -1,87 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'welect'; -const DEFAULT_DOMAIN = 'www.welect.de'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['wlt'], - supportedMediaTypes: ['video'], - - // 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) { - return utils.deepAccess(bid, 'mediaTypes.video.context') === 'instream' && !!(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) { - return validBidRequests.map(bidRequest => { - let rawSizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; - let size = rawSizes[0] - - let domain = bidRequest.params.domain || DEFAULT_DOMAIN; - - let url = `https://${domain}/api/v2/preflight/by_alias/${bidRequest.params.placementId}`; - - let gdprConsent = null; - - if (bidRequest && bidRequest.gdprConsent) { - gdprConsent = { - gdpr_consent: - { - gdpr_applies: bidRequest.gdprConsent.gdprApplies, - gdpr_consent: bidRequest.gdprConsent.gdprConsent - } - } - } - - const data = { - width: size[0], - height: size[1], - bid_id: bidRequest.bidId, - ...gdprConsent - } - - return { - method: 'POST', - url: url, - data: data, - options: { - contentType: 'application/json', - withCredentials: false, - crossOrigin: true, - } - } - }); - }, - /** - * 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.available !== true) { - return []; - } - - const bidResponses = []; - const bidResponse = responseBody.bidResponse; - bidResponses.push(bidResponse); - return bidResponses; - } -} -registerBidder(spec); 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..b73758e9717 --- /dev/null +++ b/modules/winrBidAdapter.js @@ -0,0 +1,614 @@ +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'; +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 = utils.convertCamelToUnderscore(param); + if ( + param === 'segments' && + utils.isArray(userObjBid.params.user[param]) + ) { + let segs = []; + userObjBid.params.user[param].forEach((val) => { + if (utils.isNumber(val)) { + segs.push({ id: val }); + } else if (utils.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, utils.deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); + 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, utils.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}`; + } + 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.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 = utils.convertTypes( + { + member: 'string', + invCode: 'string', + placementId: 'number', + keywords: utils.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 = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return 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 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 + ); + } + } + return result; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + let options = { + withCredentials: true + }; + + let endpointUrl = URL; + + if (!hasPurpose1Consent(bidderRequest)) { + endpointUrl = URL_SIMPLE; + } + + if ( + utils.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 = utils.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) { + utils.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 (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = utils.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 ( + 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 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 (!utils.isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.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..c08429ecc63 100644 --- a/modules/wipesBidAdapter.js +++ b/modules/wipesBidAdapter.js @@ -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/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9c2b6abf475..c05ecbc15fc 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -8,10 +8,12 @@ const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' const BID_RESPONSE_TTL_SEC = 300 const CURRENCY_CODE = 'EUR' -const OUTSTREAMPLAYER_URL = 'https://ad2.movad.net/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' +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, + gvlid: GVLID, supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { @@ -37,7 +39,7 @@ export const spec = { utils._each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) if (bid.params.targeting) { - query.t = createQueryString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting) } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { query.ids = createUserIdString(bid.userIdAsEids) @@ -47,12 +49,21 @@ export const spec = { query[prop] = bid.params.customParams[prop] } } + if (bid.schain && utils.isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { + query.schain = createSchainString(bid.schain) + } }) - if (bidderRequest && bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + query.pubref = bidderRequest.refererInfo.referer + } + + if (bidderRequest.gdprConsent) { + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + if (query.gdpr) { + query.consent = bidderRequest.gdprConsent.consentString + } } } @@ -62,7 +73,8 @@ export const spec = { return { method: 'GET', url: `${ENDPOINT}/yp/${adslots}?${queryString}`, - validBidRequests: validBidRequests + validBidRequests: validBidRequests, + queryParams: query } }, @@ -74,6 +86,7 @@ export const spec = { interpretResponse: function (serverResponse, originalBidRequest) { const bidResponses = [] const timestamp = Date.now() + const reqParams = originalBidRequest.queryParams originalBidRequest.validBidRequests.forEach(function (bidRequest) { if (!serverResponse.body) { @@ -85,23 +98,29 @@ export const spec = { }) if (matchedBid) { - const primarysize = bidRequest.sizes.length === 2 && !utils.isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] - const customsize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : primarysize + const adUnitSize = bidRequest.sizes.length === 2 && !utils.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 bidResponse = { requestId: bidRequest.bidId, cpm: matchedBid.price / 100, - width: customsize[0], - height: customsize[1], + width: adSize[0], + height: adSize[1], creativeId: '' + matchedBid.id, dealId: (matchedBid['c.dealid']) ? matchedBid['c.dealid'] : matchedBid.pid, currency: CURRENCY_CODE, netRevenue: false, ttl: BID_RESPONSE_TTL_SEC, referrer: '', - ad: `` + ad: ``, + meta: { + advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' + } } if (isVideo(bidRequest, adType)) { @@ -111,8 +130,7 @@ export const spec = { bidResponse.height = playersize[1] } bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/${customsize[0]}x${customsize[1]}?ts=${timestamp}${extId}` - + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, @@ -192,12 +210,58 @@ function createQueryString (obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])) + let val = obj[p] + if (p !== 'schain') { + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) + } else { + str.push(p + '=' + val) + } } } return str.join('&') } +/** + * Creates an unencoded targeting string out of an object with key-values + * @param {Object} obj + * @returns {String} + */ +function createTargetingString (obj) { + let str = [] + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + let key = p + let val = obj[p] + str.push(key + '=' + val) + } + } + return str.join('&') +} + +/** + * Creates a string out of a schain object + * @param {Object} schain + * @returns {String} + */ +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'] + const nodesString = schain.nodes.reduce((acc, node) => { + return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}` + }, '') + return `${ver},${complete}${nodesString}` +} + +/** + * Encodes URI Component with exlamation mark included. Needed for schain object. + * @param {String} str + * @returns {String} + */ +function encodeURIComponentWithBangIncluded(str) { + return encodeURIComponent(str).replace(/!/g, '%21') +} + /** * Handles an outstream response after the library is loaded * @param {Object} bid diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index 37897b83f12..a7a3f2715dc 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -21,7 +21,6 @@ Module that connects to Yieldlab's demand sources params: { adslotId: "5220336", supplyId: "1381604", - adSize: "728x90", targeting: { key1: "value1", key2: "value2" @@ -41,8 +40,7 @@ Module that connects to Yieldlab's demand sources bidder: "yieldlab", params: { adslotId: "5220339", - supplyId: "1381604", - adSize: "640x480" + supplyId: "1381604" } }] } diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js index 87a598bd335..9398e2c7816 100644 --- a/modules/yieldliftBidAdapter.js +++ b/modules/yieldliftBidAdapter.js @@ -96,6 +96,9 @@ export const spec = { creativeId: bid.crid, netRevenue: DEFAULT_NET_REVENUE, currency: DEFAULT_CURRENCY, + meta: { + adomain: bid.adomain + } }) }) } else { diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 55495979ea4..a959ab92b61 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,109 +1,159 @@ import * as utils 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'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; const TIME_TO_LIVE = 300; const NET_REVENUE = true; -const SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; -const localWindow = utils.getWindowTop(); +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 = ['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 DEFAULT_PLAYBACK_METHOD = 2; +const DEFAULT_START_DELAY = 0; +const VAST_TIMEOUT = 15000; +const MAX_BANNER_REQUEST_URL_LENGTH = 8000; +const BANNER_REQUEST_PROPERTIES_TO_REDUCE = ['description', 'title', 'pr', 'page_url']; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: ['banner'], + supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @param {object} bid, bid to validate * @return boolean, true if valid, otherwise false */ - isBidRequestValid: function(bid) { - return !!(bid && bid.adUnitCode && bid.bidId); + isBidRequestValid: function (bid) { + return !!(bid && bid.adUnitCode && bid.bidId && (hasBannerMediaType(bid) || hasVideoMediaType(bid)) && + validateVideoParams(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. + * @param {BidderRequest} bidderRequest bidder request object. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bidRequests, bidderRequest) { - let serverRequest = { - p: [], - page_url: bidderRequest.refererInfo.referer, - bust: new Date().getTime().toString(), - pr: bidderRequest.refererInfo.referer, - scrd: localWindow.devicePixelRatio || 0, - dnt: getDNT(), - e: getEnvironment(), - description: getPageDescription(), - title: localWindow.document.title || '', - w: localWindow.innerWidth, - h: localWindow.innerHeight, - userConsent: - JSON.stringify({ + buildRequests: function (bidRequests, bidderRequest) { + const bannerBidRequests = bidRequests.filter(request => hasBannerMediaType(request)); + const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); + + let serverRequests = []; + 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: - bidderRequest && bidderRequest.gdprConsent - ? bidderRequest.gdprConsent.gdprApplies - : '', - cmp: - bidderRequest && bidderRequest.gdprConsent - ? bidderRequest.gdprConsent.consentString - : '', + gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' }), - us_privacy: - bidderRequest && bidderRequest.uspConsent - ? bidderRequest.uspConsent - : '', - }; - - bidRequests.forEach(request => { - serverRequest.p.push(addPlacement(request)); - const pubcid = getId(request, 'pubcid'); - if (pubcid) { - serverRequest.pubcid = pubcid; - } else if (request.crumbs) { - if (request.crumbs.pubcid) { + us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' + }; + + const mtp = window.navigator.maxTouchPoints; + if (mtp) { + serverRequest.mtp = mtp; + } + + bannerBidRequests.forEach(request => { + serverRequest.p.push(addPlacement(request)); + const pubcid = getId(request, 'pubcid'); + if (pubcid) { + serverRequest.pubcid = pubcid; + } else if (request.crumbs && request.crumbs.pubcid) { serverRequest.pubcid = request.crumbs.pubcid; } + const tdid = getId(request, 'tdid'); + if (tdid) { + serverRequest.tdid = tdid; + } + const criteoId = getId(request, 'criteoId'); + if (criteoId) { + serverRequest.cri_prebid = criteoId; + } + if (request.schain) { + serverRequest.schain = JSON.stringify(request.schain); + } + if (utils.deepAccess(request, 'params.lr_env')) { + serverRequest.ats_envelope = request.params.lr_env; + } + }); + serverRequest.p = '[' + serverRequest.p.toString() + ']'; + + // check if url exceeded max length + const url = `${BANNER_SERVER_ENDPOINT}?${utils.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++) { + extraCharacters = shortcutProperty(extraCharacters, serverRequest, BANNER_REQUEST_PROPERTIES_TO_REDUCE[i]); + + if (extraCharacters <= 0) { + break; + } + } } - const tdid = getId(request, 'tdid'); - if (tdid) { - serverRequest.tdid = tdid; - } - const criteoId = getId(request, 'criteoId'); - if (criteoId) { - serverRequest.cri_prebid = criteoId; - } - if (request.schain) { - serverRequest.schain = - JSON.stringify(request.schain); - } - }); - serverRequest.p = '[' + serverRequest.p.toString() + ']'; - return { - method: 'GET', - url: SERVER_ENDPOINT, - data: serverRequest - }; + + serverRequests.push({ + method: 'GET', + url: BANNER_SERVER_ENDPOINT, + data: serverRequest + }); + } + + if (videoBidRequests.length > 0) { + const serverRequest = openRtbRequest(videoBidRequests, bidderRequest); + serverRequests.push({ + method: 'POST', + url: VIDEO_SERVER_ENDPOINT, + data: serverRequest + }); + } + return serverRequests; }, + /** * Makes Yieldmo Ad Server response compatible to Prebid specs - * @param serverResponse successful response from Ad Server + * @param {ServerResponse} serverResponse successful response from Ad Server + * @param {ServerRequest} bidRequest * @return {Bid[]} an array of bids */ - interpretResponse: function(serverResponse) { + interpretResponse: function (serverResponse, bidRequest) { let bids = []; - let data = serverResponse.body; + const data = serverResponse.body; if (data.length > 0) { data.forEach(response => { - if (response.cpm && response.cpm > 0) { - bids.push(createNewBid(response)); + if (response.cpm > 0) { + bids.push(createNewBannerBid(response)); } }); } + if (data.seatbid) { + const seatbids = data.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + seatbids.forEach(bid => bids.push(createNewVideoBid(bid, bidRequest))); + } return bids; }, - getUserSyncs: function() { + + getUserSyncs: function () { return []; } }; @@ -113,11 +163,26 @@ registerBidder(spec); * Helper Functions ***************************************/ +/** + * @param {BidRequest} bidRequest bid request + */ +function hasBannerMediaType(bidRequest) { + return !!utils.deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!utils.deepAccess(bidRequest, 'mediaTypes.video'); +} + /** * Adds placement information to array * @param request bid request */ function addPlacement(request) { + const gpid = utils.deepAccess(request, 'ortb2Imp.ext.data.pbadslot'); const placementInfo = { placement_id: request.adUnitCode, callback_id: request.bidId, @@ -127,18 +192,22 @@ function addPlacement(request) { if (request.params.placementId) { placementInfo.ym_placement_id = request.params.placementId; } - if (request.params.bidFloor) { - placementInfo.bidFloor = request.params.bidFloor; + const bidfloor = getBidFloor(request, BANNER); + if (bidfloor) { + placementInfo.bidFloor = bidfloor; } } + if (gpid) { + placementInfo.gpid = gpid; + } return JSON.stringify(placementInfo); } /** - * creates a new bid with response information + * creates a new banner bid with response information * @param response server response */ -function createNewBid(response) { +function createNewBannerBid(response) { return { requestId: response['callback_id'], cpm: response.cpm, @@ -149,7 +218,66 @@ function createNewBid(response) { netRevenue: NET_REVENUE, ttl: TIME_TO_LIVE, ad: response.ad, + meta: { + advertiserDomains: response.adomain || [], + mediaType: BANNER, + }, + }; +} + +/** + * creates a new video bid with response information + * @param response openRTB server response + * @param bidRequest server request + */ +function createNewVideoBid(response, bidRequest) { + const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); + + let result = { + requestId: imp.id, + cpm: response.price, + width: imp.video.w, + height: imp.video.h, + creativeId: response.crid || response.adid, + currency: CURRENCY, + netRevenue: NET_REVENUE, + mediaType: VIDEO, + ttl: TIME_TO_LIVE, + vastXml: response.adm, + meta: { + advertiserDomains: response.adomain || [], + mediaType: VIDEO, + }, }; + + if (imp.video.placement && imp.video.placement !== 1) { + const renderer = Renderer.install({ + url: OUTSTREAM_VIDEO_PLAYER_URL, + config: { + width: result.width, + height: result.height, + vastTimeout: VAST_TIMEOUT, + maxAllowedVastTagRedirects: 5, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true + }, + id: imp.tagid, + loaded: false, + }); + + renderer.setRender(function (bid) { + bid.renderer.push(() => { + const { id, config } = bid.renderer; + window.YMoutstreamPlayer(bid, id, config); + }); + }); + + result.renderer = renderer; + } + + return result; } /** @@ -169,175 +297,301 @@ function getPageDescription() { if (document.querySelector('meta[name="description"]')) { return document .querySelector('meta[name="description"]') - .getAttribute('content'); // Value of the description metadata from the publisher's page. + .getAttribute('content') || ''; // Value of the description metadata from the publisher's page. } else { return ''; } } -/*************************************** - * Detect Environment Helper Functions - ***************************************/ - /** - * Represents a method for loading Yieldmo ads. Environments affect - * which formats can be loaded into the page - * Environments: - * CodeOnPage: 0, // div directly on publisher's page - * Amp: 1, // google Accelerate Mobile Pages ampproject.org - * Mraid = 2, // native loaded through the MRAID spec, without Yieldmo's SDK https://www.iab.net/media/file/IAB_MRAID_v2_FINAL.pdf - * Dfp: 4, // google doubleclick for publishers https://www.doubleclickbygoogle.com/ - * DfpInAmp: 5, // AMP page containing a DFP iframe - * SafeFrame: 10, - * DfpSafeFrame: 11,Sandboxed: 16, // An iframe that can't get to the top window. - * SuperSandboxed: 89, // An iframe without allow-same-origin - * Unknown: 90, // A default sandboxed implementation delivered by EnvironmentDispatch when all positive environment checks fail + * Gets an id from the userId object if it exists + * @param {*} request + * @param {*} idType + * @returns an id if there is one, or undefined */ +function getId(request, idType) { + return (typeof utils.deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; +} /** - * Detects what environment we're in - * @returns Environment kind + * @param {BidRequest[]} bidRequests bid request object + * @param {BidderRequest} bidderRequest bidder request object + * @return Object OpenRTB request object */ -function getEnvironment() { - if (isSuperSandboxedIframe()) { - return 89; - } else if (isDfpInAmp()) { - return 5; - } else if (isDfp()) { - return 4; - } else if (isAmp()) { - return 1; - } else if (isDFPSafeFrame()) { - return 11; - } else if (isSafeFrame()) { - return 10; - } else if (isMraid()) { - return 2; - } else if (isCodeOnPage()) { - return 0; - } else if (isSandboxedIframe()) { - return 16; - } else { - return 90; +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(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; } -} -/** - * @returns true if we are running on the top window at dispatch time - */ -function isCodeOnPage() { - return window === window.parent; -} + populateOpenRtbGdpr(openRtbRequest, bidderRequest); -/** - * @returns true if the environment is both DFP and AMP - */ -function isDfpInAmp() { - return isDfp() && isAmp(); + return openRtbRequest; } /** - * @returns true if the window is in an iframe whose id and parent element id match DFP + * @param {BidRequest} bidRequest bidder request object. + * @return Object OpenRTB's 'imp' (impression) object */ -function isDfp() { - try { - const frameElement = window.frameElement; - const parentElement = window.frameElement.parentNode; - if (frameElement && parentElement) { - return ( - frameElement.id.indexOf('google_ads_iframe') > -1 && - parentElement.id.indexOf('google_ads_iframe') > -1 - ); +function openRtbImpression(bidRequest) { + const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + const size = extractPlayerSize(bidRequest); + const imp = { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + bidfloor: getBidFloor(bidRequest, VIDEO), + ext: { + placement_id: bidRequest.params.placementId + }, + video: { + w: size[0], + h: size[1], + linearity: 1 } - return false; - } catch (e) { - return false; + }; + + const mediaTypesParams = utils.deepAccess(bidRequest, 'mediaTypes.video'); + Object.keys(mediaTypesParams) + .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) + .forEach(param => imp.video[param] = mediaTypesParams[param]); + + const videoParams = utils.deepAccess(bidRequest, 'params.video'); + Object.keys(videoParams) + .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) + .forEach(param => imp.video[param] = videoParams[param]); + + 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; +} + +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ currency: CURRENCY, mediaType, size: '*' }); + } + + return floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.bidFloor || 0; } /** - * @returns true if there is an AMP context object + * @param {BidRequest} bidRequest bidder request object. + * @return [number, number] || null Player's width and height, or undefined otherwise. */ -function isAmp() { - try { - const ampContext = window.context || window.parent.context; - if (ampContext && ampContext.pageViewId) { - return ampContext; - } - return false; - } catch (e) { - return false; +function extractPlayerSize(bidRequest) { + const sizeArr = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (utils.isArrayOfNums(sizeArr, 2)) { + return sizeArr; + } else if (utils.isArray(sizeArr) && utils.isArrayOfNums(sizeArr[0], 2)) { + return sizeArr[0]; } + return null; } /** - * @returns true if the environment is a SafeFrame. + * @param {BidRequest} bidRequest bid request object + * @param {BidderRequest} bidderRequest bidder request object + * @return Object OpenRTB's 'site' object */ -function isSafeFrame() { - return window.$sf && window.$sf.ext; +function openRtbSite(bidRequest, bidderRequest) { + let result = {}; + + const loc = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); + if (!utils.isEmpty(loc)) { + result.page = `${loc.protocol}://${loc.hostname}${loc.pathname}`; + } + + if (self === top && document.referrer) { + result.ref = document.referrer; + } + + const keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + result.keywords = keywords.content; + } + + const siteParams = utils.deepAccess(bidRequest, 'params.site'); + if (siteParams) { + Object.keys(siteParams) + .filter(param => includes(OPENRTB_VIDEO_SITEPARAMS, param)) + .forEach(param => result[param] = siteParams[param]); + } + return result; } /** - * @returns true if the environment is a dfp safe frame. + * @return Object OpenRTB's 'device' object */ -function isDFPSafeFrame() { - if (window.location && window.location.href) { - const href = window.location.href; - return ( - isSafeFrame() && - href.indexOf('google') !== -1 && - href.indexOf('safeframe') !== -1 - ); +function openRtbDevice(bidRequest) { + const ip = utils.deepAccess(bidRequest, 'params.device.ip'); + const deviceObj = { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; + if (ip) { + deviceObj.ip = ip; } - return false; + return deviceObj; } /** - * Return true if we are in an iframe and can't access the top window. + * Updates openRtbRequest with GDPR info from bidderRequest, if present. + * @param {Object} openRtbRequest OpenRTB's request to update. + * @param {BidderRequest} bidderRequest bidder request object. */ -function isSandboxedIframe() { - return window.top !== window && !window.frameElement; +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); + } + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + utils.deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); + } } /** - * Return true if we cannot document.write to a child iframe (this implies no allow-same-origin) + * Determines whether or not the given video bid request is valid. If it's not a video bid, returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false */ -function isSuperSandboxedIframe() { - const sacrificialIframe = window.document.createElement('iframe'); +function validateVideoParams(bid) { + if (!hasVideoMediaType(bid)) { + return true; + } + + const paramRequired = (paramStr, value, conditionStr) => { + let error = `"${paramStr}" is required`; + if (conditionStr) { + error += ' when ' + conditionStr; + } + throw new Error(error); + } + + const paramInvalid = (paramStr, value, expectedStr) => { + expectedStr = expectedStr ? ', expected: ' + expectedStr : ''; + value = JSON.stringify(value); + throw new Error(`"${paramStr}"=${value} is invalid${expectedStr}`); + } + + const isDefined = val => typeof val !== 'undefined'; + const validate = (fieldPath, validateCb, errorCb, errorCbParam) => { + if (fieldPath.indexOf('video') === 0) { + const valueFieldPath = 'params.' + fieldPath; + const mediaFieldPath = 'mediaTypes.' + fieldPath; + const valueParams = utils.deepAccess(bid, valueFieldPath); + const mediaTypesParams = utils.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 = utils.deepAccess(bid, fieldPath); + if (!validateCb(value)) { + errorCb(fieldPath, value, errorCbParam); + } + return value; + } + } + try { - sacrificialIframe.setAttribute('style', 'display:none'); - window.document.body.appendChild(sacrificialIframe); - sacrificialIframe.contentWindow._testVar = true; - window.document.body.removeChild(sacrificialIframe); - return false; - } catch (e) { - window.document.body.removeChild(sacrificialIframe); + validate('video.context', val => !utils.isEmpty(val), paramRequired); + + validate('params.placementId', val => !utils.isEmpty(val), paramRequired); + + validate('video.playerSize', val => utils.isArrayOfNums(val, 2) || + (utils.isArray(val) && val.every(v => utils.isArrayOfNums(v, 2))), + paramInvalid, 'array of 2 integers, ex: [640,480] or [[640,480]]'); + + validate('video.mimes', val => isDefined(val), paramRequired); + validate('video.mimes', val => utils.isArray(val) && val.every(v => utils.isStr(v)), paramInvalid, + 'array of strings, ex: ["video/mp4"]'); + + const placement = validate('video.placement', val => isDefined(val), paramRequired); + validate('video.placement', val => val >= 1 && val <= 5, paramInvalid); + if (placement === 1) { + validate('video.startdelay', val => isDefined(val), + (field, v) => paramRequired(field, v, 'placement == 1')); + validate('video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5'); + } + + validate('video.protocols', val => isDefined(val), paramRequired); + validate('video.protocols', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + paramInvalid, 'array of numbers, ex: [2,3]'); + + validate('video.api', val => isDefined(val), paramRequired); + validate('video.api', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + paramInvalid, 'array of numbers, ex: [2,3]'); + + validate('video.playbackmethod', val => !isDefined(val) || utils.isArrayOfNums(val), paramInvalid, + 'array of integers, ex: [2,6]'); + + validate('video.maxduration', val => isDefined(val), paramRequired); + validate('video.maxduration', val => utils.isInteger(val), paramInvalid); + validate('video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid); + validate('video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('params.badv', val => !isDefined(val) || utils.isArray(val), paramInvalid, + 'array of strings, ex: ["ford.com","pepsi.com"]'); + validate('params.bcat', val => !isDefined(val) || utils.isArray(val), paramInvalid, + 'array of strings, ex: ["IAB1-5","IAB1-6"]'); return true; + } catch (e) { + utils.logError(e.message); + return false; } } /** - * @returns true if the window has the attribute identifying MRAID + * Shortcut object property and check if required characters count was deleted + * + * @param {number} extraCharacters, count of characters to remove + * @param {object} target, object on which string property length should be reduced + * @param {string} propertyName, name of property to reduce + * @return {number} 0 if required characters count was removed otherwise count of how many left */ -function isMraid() { - return !!window.mraid; -} +function shortcutProperty(extraCharacters, target, propertyName) { + if (target[propertyName].length > extraCharacters) { + target[propertyName] = target[propertyName].substring(0, target[propertyName].length - extraCharacters); -/** - * Gets an id from the userId object if it exists - * @param {*} request - * @param {*} idType - * @returns an id if there is one, or undefined - */ -function getId(request, idType) { - let id; - if ( - request && - request.userId && - request.userId[idType] && - typeof request.userId === 'object' - ) { - id = request.userId[idType]; + return 0 } - return id; + + const charactersLeft = extraCharacters - target[propertyName].length; + + target[propertyName] = ''; + + return charactersLeft; } diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 0f86d2507d1..c98e2ab5c74 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -11,26 +11,96 @@ Note: Our ads will only render in mobile Connects to Yieldmo Ad Server for bids. -Yieldmo bid adapter supports Banner. +Yieldmo bid adapter supports Banner and Video. # Test Parameters + +## Banner + +Sample banner ad unit config: +```javascript +var adUnits = [{ // Banner adUnit + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) + bidFloor: .28, // optional param + lr_env: '***' // Optional. Live Ramp ATS envelope + } + }] +}]; +``` + +## Video + +Sample instream video ad unit config: +```javascript +var adUnits = [{ // Video adUnit + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 1, // required, integer + maxduration: 30, // required, integer + minduration: 15, // optional, integer + pos: 1, // optional, integer + startdelay: 10, // required if placement == 1 + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [2,6], // required, array of integers + skippable: true, // optional, boolean + skipafter: 10 // optional, integer + }, + lr_env: '***' // Optional. Live Ramp ATS envelope + } + }] +}]; +``` + +Sample out-stream video ad unit config: +```javascript +var videoAdUnit = [{ + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'outstream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 3, // required, integer ( 3,4,5 ) + maxduration: 30, // required, integer + 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 + } + }] +}]; ``` -var adUnits = [ - // Banner adUnit - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - } - bids: [{ - bidder: 'yieldmo', - params: { - placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) - bidFloor: .28 // optional param - } - }] - } -]; -``` \ No newline at end of file + +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/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index dd40e205b07..542c0917708 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -123,7 +123,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { auctionManager.getAdUnitCodes(), auctionManager.getBidsReceived() ); - if (yieldoneAnalytics.eventsStorage[currentAuctionId]) { + if (yieldoneAnalytics.eventsStorage[currentAuctionId] && yieldoneAnalytics.eventsStorage[currentAuctionId].events.length) { yieldoneAnalytics.eventsStorage[currentAuctionId].page = {url: referrers[currentAuctionId]}; yieldoneAnalytics.eventsStorage[currentAuctionId].pubId = pubId; yieldoneAnalytics.eventsStorage[currentAuctionId].wrapper_version = '$prebid.version$'; @@ -139,8 +139,8 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { } } }, - sendStat(events, auctionId) { - if (!events) return; + sendStat(data, auctionId) { + if (!data || !data.events || !data.events.length) return; delete yieldoneAnalytics.eventsStorage[auctionId]; ajax( url, @@ -148,7 +148,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { success: function() {}, error: function() {} }, - JSON.stringify(events), + JSON.stringify(data), { method: 'POST' } diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 59240878426..73c3d2e8808 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -8,6 +8,7 @@ const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +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'; export const spec = { @@ -51,6 +52,12 @@ export const spec = { payload.h = size.split('x')[1]; } + // LiveRampID + const idlEnv = utils.deepAccess(bidRequest, 'userId.idl_env'); + if (utils.isStr(idlEnv) && !utils.isEmpty(idlEnv)) { + payload.lr_env = idlEnv; + } + return { method: 'GET', url: ENDPOINT_URL, @@ -81,7 +88,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') { @@ -136,7 +146,11 @@ export const spec = { } else if (response.adm) { bidResponse.mediaType = VIDEO; bidResponse.vastXml = response.adm; - bidResponse.renderer = newRenderer(response); + if (renderId === 'cmer') { + bidResponse.renderer = newCmerRenderer(response); + } else { + bidResponse.renderer = newRenderer(response); + } } bidResponses.push(bidResponse); @@ -175,4 +189,26 @@ function outstreamRender(bid) { }); } +function newCmerRenderer(response) { + const renderer = Renderer.install({ + id: response.uid, + url: CMER_PLAYER_URL, + loaded: false, + }); + + try { + renderer.setRender(cmerRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on newRenderer', err); + } + + return renderer; +} + +function cmerRender(bid) { + bid.renderer.push(() => { + window.CMERYONEPREBID.renderPrebid(bid); + }); +} + registerBidder(spec); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index b346a26c843..2ef2d251ace 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -3,134 +3,257 @@ 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'; -const emptyUrl = ''; -const analyticsType = 'endpoint'; -const yuktamediaAnalyticsVersion = 'v2.0.0'; +const storage = getStorageManager(); +const yuktamediaAnalyticsVersion = 'v3.1.0'; let initOptions; -let auctionTimestamp; -let events = { - bids: [] + +const events = { + auctions: {} +}; +const localStoragePrefix = 'yuktamediaAnalytics_'; +const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; +const location = utils.getWindowLocation(); +const referer = getRefererInfo().referer; +const _pageInfo = { + userAgent: window.navigator.userAgent, + timezoneOffset: new Date().getTimezoneOffset(), + language: window.navigator.language, + screenWidth: window.screen.width, + screenHeight: window.screen.height, + pageViewId: utils.generateUUID(), + host: location.host, + path: location.pathname, + search: location.search, + hash: location.hash, + referer: referer, + refererDomain: utils.parseUrl(referer).host, + yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version }; -var yuktamediaAnalyticsAdapter = Object.assign(adapter( - { - emptyUrl, - analyticsType - }), { - track({ eventType, args }) { - if (typeof args !== 'undefined') { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { - args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { - events.auctionInit = args; - auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { - mapBidRequests(args).forEach(item => { events.bids.push(item) }); - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { - mapBidResponse(args, 'response'); - } else if (eventType === CONSTANTS.EVENTS.BID_WON) { - send({ - bidWon: mapBidResponse(args, 'win') - }, 'won'); - } +function getParameterByName(param) { + let vars = {}; + window.location.href.replace(location.hash, '').replace( + /[?&]+([^=&]+)=?([^&]*)?/gi, + function (m, key, value) { + vars[key] = value !== undefined ? value : ''; } + ); + return vars[param] ? vars[param] : ''; +} - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - send(events, 'auctionEnd'); - } - } -}); +function isNavigatorSendBeaconSupported() { + return ('navigator' in window) && ('sendBeacon' in window.navigator); +} -function mapBidRequests(params) { - let arr = []; - if (typeof params.bids !== 'undefined' && params.bids.length) { - params.bids.forEach(function (bid) { - arr.push({ - bidderCode: bid.bidder, - bidId: bid.bidId, - adUnitCode: bid.adUnitCode, - requestId: bid.bidderRequestId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), - renderStatus: 1, - requestTimestamp: params.auctionStart - }); - }); +function updateSessionId() { + if (isSessionIdTimeoutExpired()) { + let newSessionId = utils.generateUUID(); + storage.setDataInLocalStorage(localStoragePrefix.concat('session_id'), newSessionId); } - return arr; + initOptions.sessionId = getSessionId(); + updateSessionIdTimeout(); } -function mapBidResponse(bidResponse, status) { - if (status !== 'win') { - let bid = events.bids.filter(o => o.bidId == bidResponse.bidId || o.bidId == bidResponse.requestId)[0]; - Object.assign(bid, { - bidderCode: bidResponse.bidder, - bidId: status == 'timeout' ? bidResponse.bidId : bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: status == 'timeout' ? 3 : 2, - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp - }); - } else if (status == 'win') { - return { - bidderCode: bidResponse.bidder, - bidId: bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - renderedSize: bidResponse.size, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: 4, - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp - } - } +function updateSessionIdTimeout() { + storage.setDataInLocalStorage(localStoragePrefix.concat('session_timeout'), Date.now()); } -function send(data, status) { - let location = utils.getWindowLocation(); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.auctionInit = Object.assign({ host: location.host, path: location.pathname, search: location.search }, data.auctionInit); - } - data.initOptions = initOptions; +function isSessionIdTimeoutExpired() { + let cpmSessionTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('session_timeout')); + return Date.now() - cpmSessionTimestamp > 3600000; +} + +function getSessionId() { + return storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) ? storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) : ''; +} + +function isUtmTimeoutExpired() { + let utmTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('utm_timeout')); + return (Date.now() - utmTimestamp) > 3600000; +} - let yuktamediaAnalyticsRequestUrl = utils.buildUrl({ +function send(data, status) { + data.initOptions = Object.assign(_pageInfo, initOptions); + const yuktamediaAnalyticsRequestUrl = utils.buildUrl({ protocol: 'https', hostname: 'analytics-prebid.yuktamedia.com', - pathname: status == 'auctionEnd' ? '/api/bids' : '/api/bid/won', - search: { - auctionTimestamp: auctionTimestamp, - yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version - } + pathname: '/api/bids' }); - - ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + if (isNavigatorSendBeaconSupported()) { + window.navigator.sendBeacon(yuktamediaAnalyticsRequestUrl, JSON.stringify(data)); + } else { + ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + } } +var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + utils.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)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } + events.auctions[args.auctionId]['timeStamp'] = args.start; + args.bids.forEach(function (bidRequest) { + events.auctions[args.auctionId]['bids'][bidRequest.bidId] = { + bidder: bidRequest.bidder, + adUnit: bidRequest.adUnitCode, + sizes: utils.parseSizesInput(bidRequest.sizes).toString(), + isBid: false, + won: false, + timeout: false, + renderStatus: 'bid-requested', + bidId: bidRequest.bidId, + auctionId: args.auctionId + } + if (typeof initOptions.enableUserIdCollection !== 'undefined' && initOptions.enableUserIdCollection && typeof bidRequest['userId'] !== 'undefined') { + for (let [userIdProvider, userId] in Object.entries(bidRequest['userId'])) { + userIdProvider = typeof userIdProvider !== 'string' ? JSON.stringify(userIdProvider) : userIdProvider; + userId = typeof userId !== 'string' ? JSON.stringify(userId) : userId; + events.auctions[args.auctionId]['bids'][bidRequest.bidId]['userID-'.concat(userIdProvider)] = userId; + } + } + }); + } + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + utils.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: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + let bidResponse = events.auctions[args.auctionId]['bids'][args.requestId]; + bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.cpm = args.cpm; + bidResponse.currency = args.currency; + bidResponse.netRevenue = args.netRevenue; + bidResponse.dealId = typeof args.dealId !== 'undefined' ? args.dealId : ''; + bidResponse.mediaType = args.mediaType; + if (bidResponse.mediaType === 'native') { + bidResponse.nativeTitle = typeof args['native']['title'] !== 'undefined' ? args['native']['title'] : ''; + bidResponse.nativeSponsoredBy = typeof args['native']['sponsoredBy'] !== 'undefined' ? args['native']['sponsoredBy'] : ''; + } + bidResponse.timeToRespond = args.timeToRespond; + bidResponse.requestTimestamp = args.requestTimestamp; + bidResponse.responseTimestamp = args.responseTimestamp; + bidResponse.bidForSize = args.size; + for (const [adserverTargetingKey, adserverTargetingValue] of Object.entries(args.adserverTargeting)) { + if (['body', 'icon', 'image', 'linkurl', 'host', 'path'].every((ele) => !strIncludes(adserverTargetingKey, ele))) { + bidResponse['adserverTargeting-' + adserverTargetingKey] = adserverTargetingValue; + } + } + bidResponse.renderStatus = 'bid-response-received'; + } + } + break; + case CONSTANTS.EVENTS.NO_BID: + utils.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: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const noBid = events.auctions[args.auctionId]['bids'][args.bidId]; + noBid.renderStatus = 'no-bid'; + } + } + break; + case CONSTANTS.EVENTS.BID_WON: + utils.logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const wonBid = events.auctions[args.auctionId]['bids'][args.requestId]; + wonBid.won = true; + wonBid.renderStatus = 'bid-won'; + send({ 'bids': [wonBid] }, 'won'); + } + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + utils.logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); + if (args.length) { + args.forEach(timeout => { + if (typeof timeout !== 'undefined' && typeof timeout.auctionId !== 'undefined' && timeout.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const timeoutBid = events.auctions[timeout.auctionId].bids[timeout.bidId]; + timeoutBid.timeout = true; + timeoutBid.renderStatus = 'bid-timedout'; + } + } + }); + } + break; + case CONSTANTS.EVENTS.AUCTION_END: + utils.logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + const bids = Object.values(events.auctions[args.auctionId]['bids']); + send({ 'bids': bids }, 'auctionEnd'); + } + break; + } + } + } +}); + +yuktamediaAnalyticsAdapter.buildUtmTagData = function (options) { + let utmTagData = {}; + let utmTagsDetected = false; + if (typeof options.enableUTMCollection !== 'undefined' && options.enableUTMCollection) { + utmTags.forEach(function (utmTagKey) { + let utmTagValue = getParameterByName(utmTagKey); + if (utmTagValue !== '') { + utmTagsDetected = true; + } + utmTagData[utmTagKey] = utmTagValue; + }); + utmTags.forEach(function (utmTagKey) { + if (utmTagsDetected) { + storage.setDataInLocalStorage(localStoragePrefix.concat(utmTagKey), utmTagData[utmTagKey]); + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } else { + if (!isUtmTimeoutExpired()) { + utmTagData[utmTagKey] = storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) ? storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) : ''; + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } + } + }); + } + return utmTagData; +}; + yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics; yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { - initOptions = config.options; + 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.'); + return; + } + } + initOptions = Object.assign({}, config.options, this.buildUtmTagData(config.options)); yuktamediaAnalyticsAdapter.originEnableAnalytics(config); }; diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md index a21675b6b1d..af47985c834 100644 --- a/modules/yuktamediaAnalyticsAdapter.md +++ b/modules/yuktamediaAnalyticsAdapter.md @@ -1,5 +1,5 @@ # Overview -Module Name: YuktaMedia Analytics Adapter +Module Name: YuktaOne Analytics by YuktaMedia Module Type: Analytics Adapter @@ -15,8 +15,11 @@ Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com { provider: 'yuktamedia', options : { - pubId : 50357 //id provided by YuktaMedia LLP - pubKey: 'xxx' //key provided by YuktaMedia LLP + pubId : 50357, // id provided by YuktaMedia LLP + pubKey: 'xxx', // key provided by YuktaMedia LLP + enableUTMCollection: true, // set true if want to collect utm info + enableSession: true, // set true if want to collect information by sessions + enableUserIdCollection: true // set true if want to collect user ID module info } } ``` 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/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js new file mode 100644 index 00000000000..8f26cc827d6 --- /dev/null +++ b/modules/zeotapIdPlusIdSystem.js @@ -0,0 +1,64 @@ +/** + * This module adds Zeotap to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/zeotapIdPlusIdSystem + * @requires module:modules/userId + */ +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const ZEOTAP_COOKIE_NAME = 'IDP'; +const ZEOTAP_VENDOR_ID = 301; +const ZEOTAP_MODULE_NAME = 'zeotapIdPlus'; + +function readCookie() { + return storage.cookiesAreEnabled() ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; +} + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; +} + +export function getStorage() { + return getStorageManager(ZEOTAP_VENDOR_ID, ZEOTAP_MODULE_NAME); +} + +export const storage = getStorage(); + +/** @type {Submodule} */ +export const zeotapIdPlusSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: ZEOTAP_MODULE_NAME, + /** + * Vendor ID of Zeotap + * @type {Number} + */ + gvlid: ZEOTAP_VENDOR_ID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ + decode(value) { + const id = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + return id ? { + 'IDP': JSON.parse(atob(id)) + } : 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() { + const id = readCookie() || readFromLocalStorage(); + return id ? { id } : undefined; + } +}; +submodule('userId', zeotapIdPlusSubmodule); diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js new file mode 100644 index 00000000000..c6ce7e857ba --- /dev/null +++ b/modules/zetaBidAdapter.js @@ -0,0 +1,190 @@ +import * as utils 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?in=1&pub='; +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; + } + + if (!(bid.params.user && + bid.params.user.buyeruid)) { + utils.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'); + return false; + } + + if (!(bid.params.device.geo && + bid.params.device.geo.country)) { + utils.logWarn('Invalid bid request - missing required geo data'); + return false; + } + + if (!bid.params.definerId) { + utils.logWarn('Invalid bid request - missing required definer 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) + }; + let payload = { + id: bidderRequest.auctionId, + imp: [impData], + site: params.site ? params.site : {}, + app: params.app ? params.app : {}, + device: params.device ? params.device : {}, + user: params.user ? params.user : {}, + at: params.at, + tmax: params.tmax, + wseat: params.wseat, + bseat: params.bseat, + allimps: params.allimps, + cur: [DEFAULT_CUR], + wlang: params.wlang, + bcat: params.bcat, + 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; + + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs.ext = Object.assign( + payload.regs.ext, + {gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0} + ); + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user.ext = Object.assign( + payload.user.ext, + {consent: request.gdprConsent.consentString} + ); + } + const postUrl = params.definerId !== PREBID_DEFINER_ID ? ENDPOINT_URL.concat('/', params.definerId) : ENDPOINT_URL; + return { + method: 'POST', + url: postUrl, + 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 + }; + 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 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.concat(definerId) + }); + } + 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/zetaBidAdapter.md b/modules/zetaBidAdapter.md new file mode 100644 index 00000000000..e0f7271a4f1 --- /dev/null +++ b/modules/zetaBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Zeta Bidder Adapter +Module Type: Bidder Adapter +Maintainer: DL-ZetaDSP-Supply-Engineering@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's demand sources + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: 'zeta_global', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + definerId: '44253', + test: 1 + } + } + ] + } + ]; +``` diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md new file mode 100644 index 00000000000..d2950bce6b9 --- /dev/null +++ b/modules/zetaSspBidAdapter.md @@ -0,0 +1,42 @@ +# Overview + +``` +Module Name: Zeta Ssp Bidder Adapter +Module Type: Bidder Adapter +Maintainer: miakovlev@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's SSP + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + 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_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js new file mode 100644 index 00000000000..e1ca61357f0 --- /dev/null +++ b/modules/zeta_global_sspBidAdapter.js @@ -0,0 +1,287 @@ +import * as utils 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 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)) { + 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; + 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.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) { + utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + utils.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 + }; + } + 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 = utils.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 (utils.isArray(videoParams.playerSize[0])) { + video.w = parseInt(videoParams.playerSize[0][0], 10); + video.h = parseInt(videoParams.playerSize[0][1], 10); + } else if (utils.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 = utils.isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = utils.isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = utils.isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = utils.isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + utils.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) { + utils.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); +} + +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 2fc53a75352..50296a6ae96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,60 +1,54 @@ { "name": "prebid.js", - "version": "3.24.0-pre", + "version": "5.3.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "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.10.1" + "@babel/highlight": "7.14.5" } }, "@babel/compat-data": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", - "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } + "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.10.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.2.tgz", - "integrity": "sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.2", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helpers": "^7.10.1", - "@babel/parser": "^7.10.2", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "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.7", + "@babel/template": "7.14.5", + "@babel/traverse": "7.14.7", + "@babel/types": "7.14.5", + "convert-source-map": "1.8.0", + "debug": "4.3.1", + "gensync": "1.0.0-beta.2", + "json5": "2.2.0", + "semver": "6.3.0", + "source-map": "0.5.7" }, "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" } }, "ms": { @@ -66,381 +60,460 @@ } }, "@babel/generator": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", - "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "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.10.2", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "@babel/types": "7.14.5", + "jsesc": "2.5.2", + "source-map": "0.5.7" } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", + "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.1" + "@babel/types": "7.14.5" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", - "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", + "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.1", - "@babel/types": "^7.10.1" + "@babel/helper-explode-assignable-expression": "7.14.5", + "@babel/types": "7.14.5" } }, "@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "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.1", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "@babel/compat-data": "7.14.7", + "@babel/helper-validator-option": "7.14.5", + "browserslist": "4.16.6", + "semver": "6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", + "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.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" + "@babel/helper-annotate-as-pure": "7.14.5", + "@babel/helper-function-name": "7.14.5", + "@babel/helper-member-expression-to-functions": "7.14.7", + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "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.1", - "@babel/helper-regex": "^7.10.1", - "regexpu-core": "^4.7.0" + "@babel/helper-annotate-as-pure": "7.14.5", + "regexpu-core": "4.7.1" } }, - "@babel/helper-define-map": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", - "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "@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.14.5", + "@babel/helper-module-imports": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5", + "@babel/traverse": "7.14.7", + "debug": "4.3.1", + "lodash.debounce": "4.0.8", + "resolve": "1.20.0", + "semver": "6.3.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/helper-explode-assignable-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", - "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", + "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/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "7.14.5" } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "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.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "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.1" + "@babel/types": "7.14.5" } }, "@babel/helper-hoist-variables": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", - "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", + "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.1" + "@babel/types": "7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", - "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "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.1" + "@babel/types": "7.14.5" } }, "@babel/helper-module-imports": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz", - "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==", + "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.10.1" + "@babel/types": "7.14.5" } }, "@babel/helper-module-transforms": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", - "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", + "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.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "@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.7", + "@babel/types": "7.14.5" } }, "@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "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.10.1" + "@babel/types": "7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz", - "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", + "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-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "@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": { - "lodash": "^4.17.13" + "@babel/helper-annotate-as-pure": "7.14.5", + "@babel/helper-wrap-function": "7.14.5", + "@babel/types": "7.14.5" } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", - "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", + "@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-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-member-expression-to-functions": "7.14.7", + "@babel/helper-optimise-call-expression": "7.14.5", + "@babel/traverse": "7.14.7", + "@babel/types": "7.14.5" } }, - "@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "@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/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "7.14.5" } }, - "@babel/helper-simple-access": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", - "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", + "@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/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "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.10.1" + "@babel/types": "7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "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.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", + "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.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-function-name": "7.14.5", + "@babel/template": "7.14.5", + "@babel/traverse": "7.14.7", + "@babel/types": "7.14.5" } }, "@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "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.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/template": "7.14.5", + "@babel/traverse": "7.14.7", + "@babel/types": "7.14.5" } }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "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.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "7.14.5", + "chalk": "2.4.2", + "js-tokens": "4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", - "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", + "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.1", - "@babel/helper-remap-async-to-generator": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", - "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", + "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.6", + "@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.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-class-features-plugin": "7.14.6", + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-class-static-block": "7.14.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "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.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-export-namespace-from": "7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "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.10.1", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-logical-assignment-operators": "7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "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.10.1", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", - "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", + "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.10.1", - "@babel/plugin-syntax-numeric-separator": "^7.10.1" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-numeric-separator": "7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", - "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", + "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/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "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.10.1", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", - "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", + "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.10.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", - "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", + "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.6", + "@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-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-annotate-as-pure": "7.14.5", + "@babel/helper-create-class-features-plugin": "7.14.6", + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "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.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-async-generators": { @@ -449,16 +522,25 @@ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz", - "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==", + "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.14.5" + } + }, + "@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.10.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-dynamic-import": { @@ -467,7 +549,16 @@ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" + } + }, + "@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.14.5" } }, "@babel/plugin-syntax-json-strings": { @@ -476,7 +567,16 @@ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" + } + }, + "@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.14.5" } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -485,16 +585,16 @@ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz", - "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -503,7 +603,7 @@ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-optional-catch-binding": { @@ -512,7 +612,7 @@ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-syntax-optional-chaining": { @@ -521,470 +621,503 @@ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "7.14.5" + } + }, + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", + "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.10.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "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.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "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.10.1", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", - "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", + "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.10.1", - "@babel/helper-define-map": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "globals": "^11.1.0" + "@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.12.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", - "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-builder-binary-assignment-operator-visitor": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-function-name": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", - "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", + "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.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@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.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-module-transforms": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "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.8.3" + "@babel/helper-create-regexp-features-plugin": "7.14.5" } }, "@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", + "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.1", - "@babel/helper-replace-supers": "^7.10.1" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/helper-replace-supers": "7.14.5" } }, "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", - "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", + "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" + "regenerator-transform": "0.14.5" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "7.14.5" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", + "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.1", - "@babel/helper-regex": "^7.10.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", - "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", - "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", + "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.1" + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", + "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.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "7.14.5", + "@babel/helper-plugin-utils": "7.14.5" } }, "@babel/preset-env": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz", - "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.1", - "@babel/helper-compilation-targets": "^7.10.2", - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-proposal-async-generator-functions": "^7.10.1", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-dynamic-import": "^7.10.1", - "@babel/plugin-proposal-json-strings": "^7.10.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-numeric-separator": "^7.10.1", - "@babel/plugin-proposal-object-rest-spread": "^7.10.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.1", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.1", - "@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", - "@babel/plugin-syntax-numeric-separator": "^7.10.1", - "@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.1", - "@babel/plugin-transform-arrow-functions": "^7.10.1", - "@babel/plugin-transform-async-to-generator": "^7.10.1", - "@babel/plugin-transform-block-scoped-functions": "^7.10.1", - "@babel/plugin-transform-block-scoping": "^7.10.1", - "@babel/plugin-transform-classes": "^7.10.1", - "@babel/plugin-transform-computed-properties": "^7.10.1", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-dotall-regex": "^7.10.1", - "@babel/plugin-transform-duplicate-keys": "^7.10.1", - "@babel/plugin-transform-exponentiation-operator": "^7.10.1", - "@babel/plugin-transform-for-of": "^7.10.1", - "@babel/plugin-transform-function-name": "^7.10.1", - "@babel/plugin-transform-literals": "^7.10.1", - "@babel/plugin-transform-member-expression-literals": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.1", - "@babel/plugin-transform-modules-commonjs": "^7.10.1", - "@babel/plugin-transform-modules-systemjs": "^7.10.1", - "@babel/plugin-transform-modules-umd": "^7.10.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.10.1", - "@babel/plugin-transform-object-super": "^7.10.1", - "@babel/plugin-transform-parameters": "^7.10.1", - "@babel/plugin-transform-property-literals": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.1", - "@babel/plugin-transform-reserved-words": "^7.10.1", - "@babel/plugin-transform-shorthand-properties": "^7.10.1", - "@babel/plugin-transform-spread": "^7.10.1", - "@babel/plugin-transform-sticky-regex": "^7.10.1", - "@babel/plugin-transform-template-literals": "^7.10.1", - "@babel/plugin-transform-typeof-symbol": "^7.10.1", - "@babel/plugin-transform-unicode-escapes": "^7.10.1", - "@babel/plugin-transform-unicode-regex": "^7.10.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.2", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "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.3", + "babel-plugin-polyfill-regenerator": "0.2.2", + "core-js-compat": "3.15.1", + "semver": "6.3.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==", + "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/helper-plugin-utils": "7.14.5", + "@babel/plugin-proposal-unicode-property-regex": "7.14.5", + "@babel/plugin-transform-dotall-regex": "7.14.5", + "@babel/types": "7.14.5", + "esutils": "2.0.3" } }, "@babel/runtime": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", - "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "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.7" + }, + "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": { - "regenerator-runtime": "^0.13.4" + "core-js-pure": "3.15.1", + "regenerator-runtime": "0.13.7" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "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.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "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.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "7.14.5", + "@babel/parser": "7.14.7", + "@babel/types": "7.14.5" } }, "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "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.3.1", + "globals": "11.12.0" }, "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" } }, "ms": { @@ -996,46 +1129,129 @@ } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "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.1", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "@babel/helper-validator-identifier": "7.14.5", + "to-fast-properties": "2.0.0" } }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "@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" + "ajv": "6.12.6", + "debug": "4.3.1", + "espree": "7.3.1", + "globals": "13.9.0", + "ignore": "4.0.6", + "import-fresh": "3.3.0", + "js-yaml": "3.14.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.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.4.1" + } + }, + "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.3" + } + }, + "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.10", + "esprima": "4.0.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 + }, + "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": "1.0.2", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "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": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.6.0", - "through2": "^2.0.3" + "acorn": "6.4.2", + "normalize-path": "3.0.0", + "postcss": "7.0.36", + "source-map": "0.6.1", + "through2": "3.0.2" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "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": { - "remove-trailing-separator": "^1.0.1" + "chalk": "2.4.2", + "source-map": "0.6.1", + "supports-color": "6.1.0" } }, "source-map": { @@ -1043,6 +1259,25 @@ "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": "3.6.0" + } } } }, @@ -1052,8 +1287,8 @@ "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" + "normalize-path": "2.1.1", + "through2": "2.0.5" }, "dependencies": { "normalize-path": { @@ -1062,1048 +1297,467 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" } } } }, "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "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/console": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", - "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "@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": { - "@jest/source-map": "^24.9.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "@jest/core": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", - "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", - "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" + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.1", + "@types/node": "15.12.4", + "@types/yargs": "16.0.3", + "chalk": "4.1.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==", + "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-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==", + "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.3.0", + "supports-color": "7.2.0" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "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": { - "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" - } - } + "color-name": "1.1.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==", + "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 }, - "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" - } - } - } + "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 }, - "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==", + "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/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 - }, - "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" + "has-flag": "4.0.0" } } } }, - "@jest/environment": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", - "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "@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": { - "@jest/fake-timers": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^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": "*" - } - } + "convert-source-map": "1.8.0", + "istanbul-lib-instrument": "4.0.3", + "loader-utils": "2.0.0", + "merge-source-map": "1.1.0", + "schema-utils": "2.7.1" } }, - "@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==", + "@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": { - "@jest/types": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^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": "*" - } - }, - "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 - }, - "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" - } - } + "type-detect": "4.0.8" } }, - "@jest/reporters": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", - "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", - "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 - } + "@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" } }, - "@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==", + "@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": { - "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 - } + "@sinonjs/commons": "1.8.3", + "array-from": "2.1.1", + "lodash": "4.17.21" } }, - "@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==", + "@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": { - "@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": "*" - } - } + "defer-to-connect": "2.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==", - "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" - } - }, - "@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==", - "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" - } - } - } + "@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 }, - "@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "@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/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.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": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "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" - } - } + "@types/http-cache-semantics": "4.0.0", + "@types/keyv": "3.1.1", + "@types/node": "15.12.4", + "@types/responselike": "1.0.0" } }, - "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz", - "integrity": "sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA==", + "@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, - "requires": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.1", - "loader-utils": "^1.4.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.6.4" - } + "optional": true }, - "@kiosked/ulid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@kiosked/ulid/-/ulid-3.0.0.tgz", - "integrity": "sha512-ZKt2KIgGHDaGfKt6FjYvCpDvBXZRRoE8b+wDOlAV76aXKpq6ITiSUnPYevR4y55NKDnwCvwOrjWe+aVOCAK8kQ==" + "@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true }, - "@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "@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 }, - "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "@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": { - "type-detect": "4.0.8" + "@types/node": "15.12.4" } }, - "@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==", + "@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": { - "samsam": "1.3.0" + "@types/through": "0.0.30", + "rxjs": "6.6.7" } }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "@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": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@types/istanbul-lib-coverage": "2.0.3" } }, - "@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==", + "@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": { - "defer-to-connect": "^2.0.0" + "@types/istanbul-lib-report": "3.0.0" } }, - "@types/babel__core": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.8.tgz", - "integrity": "sha512-KXBiQG2OXvaPWFPDS1rD8yV9vO0OuWIqAEqLsbfX0oU2REN5KuoMnZ1gClWcBhO5I3n6oTVAmrMufOvRqdmFTQ==", + "@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": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@types/node": "15.12.4" } }, - "@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==", + "@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": { - "@babel/types": "^7.0.0" + "@types/lodash": "4.14.170" } }, - "@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==", + "@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": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@types/lodash": "4.14.170" } }, - "@types/babel__traverse": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", - "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", + "@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": { - "@babel/types": "^7.3.0" + "@types/lodash": "4.14.170" } }, - "@types/cacheable-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", - "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "@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/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" + "@types/unist": "2.0.3" } }, - "@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==", + "@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/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==", + "@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/istanbul-lib-coverage": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz", - "integrity": "sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==", + "@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/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==", + "@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/istanbul-lib-coverage": "*" + "@types/node": "15.12.4" } }, - "@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==", + "@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/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" + "@types/node": "15.12.4" } }, - "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "@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": "15.12.4" + } + }, + "@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/keyv": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", - "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "@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/node": "15.12.4" } }, - "@types/node": { - "version": "14.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.9.tgz", - "integrity": "sha512-0sCTiXKXELOBxvZLN4krQ0FPOAA7ij+6WwvD0k/PHd9/KAkr4dXel5J9fh6F4x1FwAQILqAWkmpeuS6mjf1iKA==", + "@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": "15.12.4" + } + }, + "@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/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "@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/node": "*" + "@types/expect": "1.20.4", + "@types/node": "15.12.4" } }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "@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": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "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": "20.2.0" } }, "@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==", + "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": { @@ -2113,72 +1767,198 @@ "dev": true, "optional": true, "requires": { - "@types/node": "*" + "@types/node": "15.12.4" } }, - "@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==", + "@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, + "requires": { + "@babel/parser": "7.14.7", + "@babel/types": "7.14.5", + "@vue/shared": "3.1.2", + "estree-walker": "2.0.2", + "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 + } + } + }, + "@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/logger": "6.0.16", - "browserstack-local": "^1.4.5", - "got": "^11.0.2" + "@vue/compiler-core": "3.1.2", + "@vue/shared": "3.1.2" } }, - "@wdio/cli": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-6.1.16.tgz", - "integrity": "sha512-q7JaEiLU2mdOibeKAQFqdWTS2evdkwgWSft1rmWDN7idiV39uncTTUcwlXBKE2a9yDk/8qn6EEXdBLthOCfyOA==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", - "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", - "log-update": "^4.0.0", - "webdriverio": "6.1.16", - "yargs": "^15.0.1", - "yarn-install": "^1.0.0" + "@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.14.7", + "@babel/types": "7.14.5", + "@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.2", + "hash-sum": "2.0.0", + "lru-cache": "5.1.1", + "magic-string": "0.25.7", + "merge-source-map": "1.1.0", + "postcss": "8.3.5", + "postcss-modules": "4.1.3", + "postcss-selector-parser": "6.0.6", + "source-map": "0.6.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==", + "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": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "yallist": "3.1.1" } }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "optional": 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==", + "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 + }, + "@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.8", + "got": "11.8.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.6", + "@types/fs-extra": "9.0.11", + "@types/inquirer": "7.3.2", + "@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.1.1", + "chokidar": "3.5.2", + "cli-spinners": "2.6.0", + "ejs": "3.1.6", + "fs-extra": "10.0.0", + "inquirer": "8.1.1", + "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.1", + "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.1.1", + "loglevel": "1.7.1", + "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": { - "color-name": "~1.1.4" + "ansi-styles": "4.3.0", + "supports-color": "7.2.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": { @@ -2187,120 +1967,142 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "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" - } - }, "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": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "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-locate": "^4.1.0" + "has-flag": "4.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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.4", + "escalade": "3.1.1", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "string-width": "4.2.2", + "y18n": "5.0.8", + "yargs-parser": "20.2.9" + } + } + } + }, + "@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.1.1", + "pretty-ms": "7.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": { - "p-try": "^2.0.0" + "color-convert": "2.0.1" } }, - "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==", + "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.2.0" + "ansi-styles": "4.3.0", + "supports-color": "7.2.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==", + "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 }, - "path-exists": { + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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==", + "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": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "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.1" + "has-flag": "4.0.0" } } } }, - "@wdio/concise-reporter": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-6.1.14.tgz", - "integrity": "sha512-QKGiIPE2sYJpQcQ5zogUogu+Yldkx/FUM4LC01lc3v0Nww7n0p0fJA3U4cKFAqyW0gXCwW7m6/9c5fDgaSv8+g==", + "@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/reporter": "6.1.14", - "chalk": "^4.0.0", - "pretty-ms": "^7.0.0" + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "deepmerge": "4.2.2", + "glob": "7.1.7" }, "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.1.1", + "loglevel": "1.7.1", + "loglevel-plugin-prefix": "0.8.4", + "strip-ansi": "6.0.0" + } + }, "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" + "color-convert": "2.0.1" } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "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" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, "color-convert": { @@ -2309,7 +2111,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -2325,70 +2127,124 @@ "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==", + "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" + "has-flag": "4.0.0" } } } }, - "@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==", - "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, "@wdio/local-runner": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-6.1.16.tgz", - "integrity": "sha512-3+pT2fcMXFAnELA6jYjVm6Nt8Il7tGL66A90UbRiT0sL2faTcD3uGAPbmzxMclsmWLh7C04ucCnFwQoTMW1emg==", - "dev": true, - "requires": { - "@wdio/logger": "6.0.16", - "@wdio/repl": "6.1.8", - "@wdio/runner": "6.1.16", - "async-exit-hook": "^2.0.1", - "stream-buffers": "^3.0.2" + "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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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.0.16", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.0.16.tgz", - "integrity": "sha512-VbH5UnQIG/3sSMV+Y38+rOdwyK9mVA9vuL7iOngoTafHwUjL1MObfN/Cex84L4mGxIgfxCu6GV48iUmSuQ7sqA==", + "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" + "chalk": "4.1.1", + "loglevel": "1.7.1", + "loglevel-plugin-prefix": "0.8.4", + "strip-ansi": "6.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==", + "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" + "color-convert": "2.0.1" } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "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" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, "color-convert": { @@ -2397,7 +2253,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -2413,325 +2269,393 @@ "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==", + "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" + "has-flag": "4.0.0" } } } }, "@wdio/mocha-framework": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-6.1.14.tgz", - "integrity": "sha512-2AmUH/v3kZoIDAMdW73AhI4tDJU3ie/2dO/DtpXJ3XFnuJ1CtklZGyCTtYHGMZ8DBj18/8Lrs/O0CjS5bAu2tw==", + "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": { - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", - "expect-webdriverio": "^1.1.5", - "mocha": "^7.0.1" + "@types/mocha": "8.2.2", + "@wdio/logger": "7.7.0", + "@wdio/types": "7.7.3", + "@wdio/utils": "7.7.3", + "expect-webdriverio": "3.1.0", + "mocha": "9.0.1" }, "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 + "@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.1.1", + "loglevel": "1.7.1", + "loglevel-plugin-prefix": "0.8.4", + "strip-ansi": "6.0.0" + } }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "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": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "color-convert": "2.0.1" } }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "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": { - "ms": "^2.1.1" + "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.5.0" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "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 }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "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 + } } }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "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": { - "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" + "locate-path": "6.0.0", + "path-exists": "4.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=", + "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": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "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": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "5.0.0" } }, "mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.1.tgz", + "integrity": "sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", + "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": "3.13.1", - "log-symbols": "3.0.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", + "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", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" + "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.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "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": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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": { - "p-try": "^2.0.0" + "yocto-queue": "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==", + "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": "^2.0.0" + "p-limit": "3.1.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 - }, "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "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==", + "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": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "picomatch": "2.3.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==", + "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": { - "ansi-regex": "^4.1.0" + "has-flag": "4.0.0" } }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "has-flag": "^3.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" + "cliui": "7.0.4", + "escalade": "3.1.1", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "string-width": "4.2.2", + "y18n": "5.0.8", + "yargs-parser": "20.2.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==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "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": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.1.14.tgz", - "integrity": "sha512-UtRLQ55i23cLQRGtFiEJty1F6AbAfiSpfIxDAiXKHbw6Rp1StwxlqHFrhNe5F48Zu4hnie46t9N/tr/cZOe0kA==", + "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": "6.1.8", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.1.8.tgz", - "integrity": "sha512-C647KvDIcOHYN24eFbiM2xE+etPEACvRYkEp7BPLyopEABDr0I3Qdb5MLhopC5eMAVHp70/WT27H1CE2v9iILQ==", + "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": "6.1.8" + "@wdio/utils": "7.7.3" } }, "@wdio/reporter": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-6.1.14.tgz", - "integrity": "sha512-Pt6P0JU0COHTpggsOoJKUJyAyQsi7xlHebBNU/DWdHHpmzYd4e9vDutjyTqXu/1zn+t+Zq+uL1IC0E4Xjv6f7w==", + "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": { - "fs-extra": "^9.0.0" + "@types/node": "14.17.4", + "@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": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-6.1.16.tgz", - "integrity": "sha512-pGRT51BGnxp4zFD1pSp6qZD/4dnbSnDyV4g/MbbFiA4PFKF41mFhaxRwIGMHcp4EYlv9gaT31UA52JaFIYyuNA==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", - "deepmerge": "^4.0.0", - "gaze": "^1.1.2", - "webdriver": "6.1.14", - "webdriverio": "6.1.16" + "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.2.2", + "gaze": "1.1.3", + "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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-6.1.14.tgz", - "integrity": "sha512-QaSBgnzllzLp9LR7/5DTkmrI8BqcznUma8ZxwUNmhvskv/oKzrmNyeCsGoiExFmCk81A9FgZiZPXey7CuaTkdw==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.7.3.tgz", + "integrity": "sha512-5elsNfZd3kbBaKY5IK5ZmdZsWZNSOCqXnM2fYryAh2RBoXbcXkak4D5PbLehusZhp6CQ7UpXEKf4BDDYfd0ebw==", "dev": true, "requires": { - "@wdio/reporter": "6.1.14", - "chalk": "^4.0.0", - "easy-table": "^1.1.1", - "pretty-ms": "^7.0.0" + "@types/easy-table": "0.0.32", + "@wdio/reporter": "7.7.3", + "@wdio/types": "7.7.3", + "chalk": "4.1.1", + "easy-table": "1.1.1", + "pretty-ms": "7.0.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==", + "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" + "color-convert": "2.0.1" } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "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" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, "color-convert": { @@ -2740,7 +2664,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -2756,33 +2680,182 @@ "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==", + "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" + "has-flag": "4.0.0" } } } }, "@wdio/sync": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-6.1.14.tgz", - "integrity": "sha512-94K0kQdrOU0aMlJ2Xsd4tWr4tPpmCFp612Ml5+ecQh4C4XD07ocfsvGs+mwI7cfF1sO6g/Hoc+XTY2D+/8En3w==", + "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.3", + "@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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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": { - "@wdio/logger": "6.0.16", - "fibers": "^4.0.1" + "@types/node": "14.17.4", + "got": "11.8.2" + }, + "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": "6.1.8", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.1.8.tgz", - "integrity": "sha512-qzvD8qCPpIpDrZ0HNOx1hTlfKY26p8WByUXgr52ll6DXxtAYXZLIJ8GAYFJUi87NVfwtv6+O7owQGSM/jtr8AQ==", + "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": "6.0.16" + "@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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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" + } + } } }, "JSONStream": { @@ -2791,15 +2864,10 @@ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "jsonparse": "1.3.1", + "through": "2.3.8" } }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" - }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -2811,14 +2879,14 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.24", + "mime-types": "2.1.31", "negotiator": "0.6.2" } }, "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "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": { @@ -2827,7 +2895,7 @@ "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", "dev": true, "requires": { - "acorn": "^4.0.3" + "acorn": "4.0.13" }, "dependencies": { "acorn": { @@ -2838,51 +2906,33 @@ } } }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "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 - } - } - }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "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 + }, + "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": "^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 - } + "acorn": "7.4.1", + "acorn-walk": "7.2.0", + "xtend": "4.0.2" } }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "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 }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, "agent-base": { @@ -2897,10 +2947,10 @@ "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" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.3.1" }, "dependencies": { "fast-deep-equal": { @@ -2918,9 +2968,9 @@ } }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, "align-text": { @@ -2929,24 +2979,18 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.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 - }, "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-buffer": "1.1.6" } } } @@ -2958,18 +3002,18 @@ "dev": true }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "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==", + "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.11.0" + "type-fest": "0.21.3" } }, "ansi-gray": { @@ -2999,7 +3043,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "ansi-wrap": { @@ -3009,13 +3053,13 @@ "dev": true }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "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" + "normalize-path": "3.0.0", + "picomatch": "2.3.0" } }, "append-buffer": { @@ -3024,41 +3068,29 @@ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "buffer-equal": "^1.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=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" + "buffer-equal": "1.0.0" } }, "archiver": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.1.tgz", - "integrity": "sha512-/YV1pU4Nhpf/rJArM23W6GTUjT0l++VbjykrCRua1TSXrn+yM8Qs7XvtwSiRse0iCe49EPNf7ktXnPsWuSb91Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.6", - "readable-stream": "^3.6.0", - "tar-stream": "^2.1.2", - "zip-stream": "^3.0.1" + "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.13", + "readable-stream": "3.6.0", + "readdir-glob": "1.1.1", + "tar-stream": "2.2.0", + "zip-stream": "4.1.0" }, "dependencies": { "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true } } }, @@ -3068,16 +3100,16 @@ "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" + "glob": "7.1.7", + "graceful-fs": "4.2.6", + "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.3.7" }, "dependencies": { "readable-stream": { @@ -3086,13 +3118,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -3104,12 +3145,19 @@ "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "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": { - "sprintf-js": "~1.0.2" + "@babel/runtime": "7.14.6", + "@babel/runtime-corejs3": "7.14.7" } }, "arr-diff": { @@ -3124,7 +3172,7 @@ "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", "dev": true, "requires": { - "make-iterator": "^1.0.0" + "make-iterator": "1.0.1" } }, "arr-flatten": { @@ -3139,7 +3187,7 @@ "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", "dev": true, "requires": { - "make-iterator": "^1.0.0" + "make-iterator": "1.0.1" } }, "arr-union": { @@ -3160,12 +3208,6 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -3183,15 +3225,23 @@ "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.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" + "call-bind": "1.0.2", + "define-properties": "1.1.3", + "es-abstract": "1.18.3", + "get-intrinsic": "1.1.1", + "is-string": "1.0.6" } }, "array-initial": { @@ -3200,8 +3250,8 @@ "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", "dev": true, "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" + "array-slice": "1.1.0", + "is-number": "4.0.0" }, "dependencies": { "is-number": { @@ -3218,7 +3268,7 @@ "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", "dev": true, "requires": { - "is-number": "^4.0.0" + "is-number": "4.0.0" }, "dependencies": { "is-number": { @@ -3241,9 +3291,9 @@ "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", "dev": true, "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" + "default-compare": "1.0.0", + "get-value": "2.0.6", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -3267,19 +3317,20 @@ "dev": true }, "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==", + "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "call-bind": "1.0.2", + "define-properties": "1.1.3", + "es-abstract": "1.18.3" } }, - "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==", + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "asn1": { @@ -3288,24 +3339,25 @@ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "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" + "bn.js": "4.12.0", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1", + "safer-buffer": "2.1.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==", + "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 } } @@ -3316,7 +3368,7 @@ "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, "requires": { - "object-assign": "^4.1.1", + "object-assign": "4.1.1", "util": "0.10.3" }, "dependencies": { @@ -3373,10 +3425,10 @@ "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" + "end-of-stream": "1.4.4", + "once": "1.4.0", + "process-nextick-args": "2.0.1", + "stream-exhaust": "1.0.2" } }, "async-each": { @@ -3403,7 +3455,7 @@ "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", "dev": true, "requires": { - "async-done": "^1.2.2" + "async-done": "1.3.2" } }, "asynckit": { @@ -3412,18 +3464,18 @@ "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==", "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", @@ -3431,9 +3483,9 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "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": { @@ -3442,9 +3494,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.3", + "js-tokens": "3.0.2" }, "dependencies": { "ansi-regex": { @@ -3465,11 +3517,11 @@ "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" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "js-tokens": { @@ -3484,7 +3536,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -3495,4508 +3547,2883 @@ } } }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "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" + "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.7.1" }, "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 + "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.5" + } + }, + "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-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "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-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 - } + "object.assign": "4.1.2" } }, - "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=", + "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-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "@babel/compat-data": "7.14.7", + "@babel/helper-define-polyfill-provider": "0.2.3", + "semver": "6.3.0" } }, - "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=", + "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-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "@babel/helper-define-polyfill-provider": "0.2.3", + "core-js-compat": "3.15.1" } }, - "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=", + "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", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" + "@babel/helper-define-polyfill-provider": "0.2.3" } }, - "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=", - "dev": true, + "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-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0" } }, - "babel-helper-define-map": { + "babel-runtime": { "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=", - "dev": true, + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "core-js": "2.6.12", + "regenerator-runtime": "0.11.1" + }, + "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==" + } } }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } + "babelify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", + "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", + "dev": true }, - "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=", + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", "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" + "arr-filter": "1.1.2", + "arr-flatten": "1.1.0", + "arr-map": "2.0.2", + "array-each": "1.0.1", + "array-initial": "1.1.0", + "array-last": "1.3.0", + "async-done": "1.3.2", + "async-settle": "1.0.0", + "now-and-later": "2.0.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=", - "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" - } + "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 }, - "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=", + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.3.0", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.2", + "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.2" + } + }, + "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.3" + } + }, + "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.3" + } + }, + "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.3" + } + } } }, - "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=", + "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": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "safe-buffer": "5.1.2" } }, - "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=", + "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": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "tweetnacl": "0.14.5" } }, - "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=", + "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": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "bluebird": "3.7.2", + "check-types": "8.0.3", + "hoopy": "0.1.4", + "tryer": "1.0.1" } }, - "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=", + "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": { - "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" + "buffers": "0.1.1", + "chainsaw": "0.1.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=", + "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": { - "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" + "file-uri-to-path": "1.0.0" } }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "buffer": "5.7.1", + "inherits": "2.0.4", + "readable-stream": "3.6.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 + } } }, - "babel-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", - "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "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": { - "@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" + "continuable-cache": "0.3.1", + "error": "7.2.1", + "raw-body": "1.1.7", + "safe-json-parse": "1.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" - } + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "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": { - "@types/yargs-parser": "*" + "bytes": "1.0.0", + "string_decoder": "0.10.31" } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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 } } }, - "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==", - "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": { - "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": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } + "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.18" } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "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": { - "babel-runtime": "^6.22.0" + "balanced-match": "1.0.2", + "concat-map": "0.0.1" } }, - "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=", + "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": { - "babel-runtime": "^6.22.0" + "fill-range": "7.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==", + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "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": { - "object.assign": "^4.1.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 + } } }, - "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==", + "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": { + "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.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.2" + } + }, + "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.2.0", + "browserify-des": "1.0.2", + "evp_bytestokey": "1.0.3" + } + }, + "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.4", + "des.js": "1.0.1", + "inherits": "2.0.3", + "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": { - "@babel/helper-plugin-utils": "^7.0.0", - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" + "bn.js": "5.2.0", + "randombytes": "2.1.0" + } + }, + "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.2.0", + "browserify-rsa": "4.1.0", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.5.4", + "inherits": "2.0.4", + "parse-asn1": "5.1.6", + "readable-stream": "3.6.0", + "safe-buffer": "5.2.1" }, "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==", + "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.11" + } + }, + "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.30001239", + "colorette": "1.2.2", + "electron-to-chromium": "1.3.755", + "escalade": "3.1.1", + "node-releases": "1.1.73" + } + }, + "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.4" + }, + "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": { - "locate-path": "^3.0.0" + "es6-promisify": "5.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==", + "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": "^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" + "ms": "2.1.3" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "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": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "agent-base": "4.3.0", + "debug": "3.2.7" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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.4", + "unzipper": "0.9.15" + }, + "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": { - "p-try": "^2.0.0" + "es6-promisify": "5.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==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ms": "2.1.3" } }, - "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 + "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.2.7" + } }, - "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.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, - "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==", + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "@types/babel__traverse": "^7.0.6" + "base64-js": "1.5.1", + "ieee754": "1.2.1" } }, - "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 - }, - "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 - }, - "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 - }, - "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=", + "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 }, - "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=", + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "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=", + "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 }, - "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=", + "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 }, - "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=", + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", "dev": true }, - "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=", + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "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=", + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", "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=", + "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 }, - "babel-plugin-system-import-transformer": { + "bytes": { "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=", - "dev": true, - "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0" - } + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "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=", + "cac": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", + "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", "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" + "camelcase-keys": "3.0.0", + "chalk": "1.1.3", + "indent-string": "3.2.0", + "minimist": "1.2.5", + "read-pkg-up": "1.0.1", + "suffix": "0.1.1", + "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.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "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.1.1" + } + }, + "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-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=", + "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": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" + "collection-visit": "1.0.0", + "component-emitter": "1.3.0", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.1", + "to-object-path": "0.3.0", + "union-value": "1.0.1", + "unset-value": "1.0.0" } }, - "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=", - "dev": true, - "requires": { - "babel-plugin-syntax-class-constructor-call": "^6.18.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "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 }, - "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=", + "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": { - "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" + "clone-response": "1.0.2", + "get-stream": "5.2.0", + "http-cache-semantics": "4.1.0", + "keyv": "4.0.3", + "lowercase-keys": "2.0.0", + "normalize-url": "6.1.0", + "responselike": "2.0.0" } }, - "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=", + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "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": { - "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" + "function-bind": "1.1.1", + "get-intrinsic": "1.1.1" } }, - "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==", + "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": { - "babel-plugin-syntax-decorators": "^6.1.18", - "babel-runtime": "^6.2.0", - "babel-template": "^6.3.0" + "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 + } } }, - "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=", - "dev": true, - "requires": { - "babel-plugin-syntax-do-expressions": "^6.8.0", - "babel-runtime": "^6.22.0" - } + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true }, - "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=", + "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": { - "babel-runtime": "^6.22.0" + "camelcase": "3.0.0", + "map-obj": "1.0.1" } }, - "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=", - "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" - } + "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 }, - "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=", - "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" - } + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "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 }, - "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=", + "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": { - "babel-runtime": "^6.22.0" + "align-text": "0.1.4", + "lazy-cache": "1.0.4" } }, - "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=", + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "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.8" } }, - "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=", + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "traverse": "0.3.9" } }, - "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=", + "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": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.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 }, - "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=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "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 }, - "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==", - "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" - } + "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 }, - "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=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "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 }, - "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=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "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 }, - "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=", + "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": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "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" } }, - "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=", + "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": { - "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" + "@types/node": "15.12.4", + "escape-string-regexp": "4.0.0", + "is-wsl": "2.2.0", + "lighthouse-logger": "1.2.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 + } } }, - "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=", + "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": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, - "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=", + "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": { - "babel-runtime": "^6.22.0" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "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.6" + } + } } }, - "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=", + "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": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "restore-cursor": "3.1.0" } }, - "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=", + "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": { - "babel-runtime": "^6.22.0" + "string-width": "4.2.2", + "strip-ansi": "6.0.0", + "wrap-ansi": "7.0.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=", + "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": { - "babel-runtime": "^6.22.0" + "mimic-response": "1.0.1" } }, - "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=", + "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": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" + "inherits": "2.0.3", + "process-nextick-args": "2.0.1", + "readable-stream": "2.3.7" }, "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=", + "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": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "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=", + "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": { - "jsesc": "~0.5.0" + "safe-buffer": "5.1.2" } } } }, - "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=", - "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" - } + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, - "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=", + "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": { - "babel-plugin-syntax-export-extensions": "^6.8.0", - "babel-runtime": "^6.22.0" + "arr-map": "2.0.2", + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, - "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=", + "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": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, - "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=", + "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": { - "babel-plugin-syntax-function-bind": "^6.8.0", - "babel-runtime": "^6.22.0" + "color-name": "1.1.3" } }, - "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" - } + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, - "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=", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.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 }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "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 }, - "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=", + "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": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "delayed-stream": "1.0.0" } }, - "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=", + "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": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "array-ify": "1.0.0", + "dot-prop": "5.3.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=", + "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": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "buffer-crc32": "0.2.13", + "crc32-stream": "4.0.2", + "normalize-path": "3.0.0", + "readable-stream": "3.6.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=", + "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": { - "regenerator-transform": "^0.10.0" + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.7", + "typedarray": "0.0.6" }, "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==", + "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.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } - } - } - }, - "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=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "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==", - "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==", + }, + "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": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" + "safe-buffer": "5.1.2" } } } }, - "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=", + "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": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" + "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 + } } }, - "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==", + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.9.0" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "1.3.3", + "utils-merge": "1.0.1" } }, - "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=", - "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" - } + "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 }, - "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=", - "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" - } + "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 }, - "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=", + "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": { - "babel-plugin-transform-class-constructor-call": "^6.24.1", - "babel-plugin-transform-export-extensions": "^6.22.0", - "babel-preset-stage-2": "^6.24.1" + "bluebird": "3.7.2" } }, - "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=", - "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" - } + "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 }, - "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=", - "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": { - "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" + "safe-buffer": "5.1.2" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "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": { - "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": { - "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 - } + "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.2", + "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" } }, - "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=", + "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": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "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==" - } + "compare-func": "2.0.0", + "q": "1.5.1" } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "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": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "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": { - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - } + "q": "1.5.1" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "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": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "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 - } + "q": "1.5.1" } }, - "babelify": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-8.0.0.tgz", - "integrity": "sha512-xVr63fKEvMWUrrIbqlHYsMcc5Zdw4FSVesAHgkgajyCE1W8gbm9rbMakqavhxKvikGYMhEcqxTwB/gQmQ6lBtw==", - "dev": true - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "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 }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "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": { - "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" + "compare-func": "2.0.0", + "lodash": "4.17.21", + "q": "1.5.1" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "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.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "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" + "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.1.0", + "conventional-commits-parser": "3.2.1", + "dateformat": "3.0.3", + "get-pkg-repo": "1.4.0", + "git-raw-commits": "2.0.10", + "git-remote-origin-url": "2.0.0", + "git-semver-tags": "4.1.1", + "lodash": "4.17.21", + "normalize-package-data": "3.0.2", + "q": "1.5.1", + "read-pkg": "3.0.0", + "read-pkg-up": "3.0.0", + "shelljs": "0.8.4", + "through2": "4.0.2" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "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": { - "is-descriptor": "^1.0.0" + "locate-path": "2.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==", + "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": "^6.0.0" + "lru-cache": "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==", + "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": { - "kind-of": "^6.0.0" + "graceful-fs": "4.2.6", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.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==", + "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": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "p-locate": "2.0.0", + "path-exists": "3.0.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 - }, - "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 - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "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 + }, + "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" + } + }, + "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.2", + "resolve": "1.20.0", + "semver": "7.3.5", + "validate-npm-package-license": "3.0.4" + } + }, + "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.3.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.2", + "json-parse-better-errors": "1.0.2" + } + }, + "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.5.0", + "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.8.9", + "resolve": "1.20.0", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" + } + }, + "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": { + "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.1.0", + "read-pkg": "3.0.0" + } + }, + "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" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "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 + } + } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "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": { - "tweetnacl": "^0.14.3" + "q": "1.5.1" } }, - "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=", + "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": { - "callsite": "1.0.0" + "q": "1.5.1" } }, - "bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "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": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" + "q": "1.5.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 + "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": { + "q": "1.5.1" + } }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "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": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" + "compare-func": "2.0.0", + "q": "1.5.1" } }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "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 }, - "binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true + "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": { + "compare-func": "2.0.0", + "conventional-commits-filter": "2.0.7", + "dateformat": "3.0.3", + "handlebars": "4.7.7", + "json-stringify-safe": "5.0.1", + "lodash": "4.17.21", + "meow": "8.1.2", + "semver": "6.3.0", + "split": "1.0.1", + "through2": "4.0.2" + }, + "dependencies": { + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2.3.8" + } + } + } }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "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, - "optional": true, "requires": { - "file-uri-to-path": "1.0.0" + "lodash.ismatch": "4.4.0", + "modify-values": "1.0.1" } }, - "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "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": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "JSONStream": "1.3.5", + "is-text-path": "1.0.1", + "lodash": "4.17.21", + "meow": "8.1.2", + "split2": "3.2.2", + "through2": "4.0.2", + "trim-off-newlines": "1.0.1" + } + }, + "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.1", + "git-raw-commits": "2.0.10", + "git-semver-tags": "4.1.1", + "meow": "8.1.2", + "q": "1.5.1" }, "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 + "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": { + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "3.6.0", + "typedarray": "0.0.6" + } } } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true + "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": { + "safe-buffer": "5.1.2" + } }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, - "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "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 }, - "body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "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": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" + "each-props": "1.3.2", + "is-plain-object": "5.0.0" }, "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=", + "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 } } }, - "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" - } + "core-js": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz", + "integrity": "sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg==" }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "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 + } } }, - "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" - } + "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==" }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "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 }, - "browser-cookies": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browser-cookies/-/browser-cookies-1.2.0.tgz", - "integrity": "sha1-/KP/ubamOq3E2MCZnGtX0Pp9KbU=" - }, - "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 + "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": { + "object-assign": "4.1.1", + "vary": "1.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==", + "coveralls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", "dev": true, "requires": { - "resolve": "1.1.7" + "js-yaml": "3.14.1", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.5", + "request": "2.88.2" }, "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "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.3" + } + }, + "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.10", + "esprima": "4.0.1" + } } } }, - "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": { + "crc-32": { "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==", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "dev": true, "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "exit-on-epipe": "1.0.1", + "printj": "1.1.2" } }, - "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==", + "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": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "crc-32": "1.2.0", + "readable-stream": "3.6.0" } }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "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": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" + "bn.js": "4.12.0", + "elliptic": "6.5.4" }, "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 - } - } - }, - "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", - "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.2", - "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==", + "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 } } }, - "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==", + "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": { - "pako": "~1.0.5" + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "md5.js": "1.3.5", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" } }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "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": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.3", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" } }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "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": { - "https-proxy-agent": "^2.2.1" + "lru-cache": "4.1.5", + "which": "1.3.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==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "isexe": "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==", - "dev": true } } }, - "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==", + "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": { - "https-proxy-agent": "^4.0.0", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "browserify-cipher": "1.0.1", + "browserify-sign": "4.2.1", + "create-ecdh": "4.0.4", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "diffie-hellman": "5.0.3", + "inherits": "2.0.3", + "pbkdf2": "3.1.2", + "public-encrypt": "4.0.3", + "randombytes": "2.1.0", + "randomfill": "1.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==", + "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": { - "https-proxy-agent": "^2.2.1", - "unzipper": "^0.9.3" + "inherits": "2.0.4", + "source-map": "0.6.1", + "source-map-resolve": "0.6.0" }, "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" - } + "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.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.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 }, - "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==", + "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": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "atob": "2.1.2", + "decode-uri-component": "0.2.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 } } }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "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 + }, + "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 + }, + "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": { - "node-int64": "^0.4.0" + "array-find-index": "1.0.2" } }, - "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==", + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "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": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "es5-ext": "0.10.53", + "type": "1.2.0" } }, - "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==", + "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": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "assert-plus": "1.0.0" } }, - "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==", + "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 }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "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 + "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 }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "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" + } }, - "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.1", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz", - "integrity": "sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=", - "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 - }, - "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=", + "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": { - "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" + "debug": "3.2.7", + "memoizee": "0.4.15", + "object-assign": "4.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 - }, - "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=", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ms": "2.1.3" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "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 } } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "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/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "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" + "decamelize": "1.2.0", + "map-obj": "1.0.1" + }, + "dependencies": { + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + } } }, - "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==", + "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 }, - "cacheable-request": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", - "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "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": { - "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" + "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 + } } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "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": { - "callsites": "^0.2.0" + "type-detect": "4.0.8" + } + }, + "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": { + "call-bind": "1.0.2", + "es-get-iterator": "1.1.2", + "get-intrinsic": "1.1.1", + "is-arguments": "1.1.0", + "is-date-object": "1.0.4", + "is-regex": "1.1.3", + "isarray": "2.0.5", + "object-is": "1.1.5", + "object-keys": "1.1.1", + "object.assign": "4.1.2", + "regexp.prototype.flags": "1.3.1", + "side-channel": "1.0.4", + "which-boxed-primitive": "1.0.2", + "which-collection": "1.0.1", + "which-typed-array": "1.1.4" }, "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "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==", + "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 }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "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 }, - "camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", + "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": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" + "kind-of": "5.1.0" }, "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "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 } } }, - "caniuse-lite": { - "version": "1.0.30001077", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001077.tgz", - "integrity": "sha512-AEzsGvjBJL0lby/87W96PyEvwN0GsYvk5LHsglLg9tW37K4BqvAvoSCdWIE13OZQ8afupqZ73+oL/1LkedN8hA==", + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", "dev": true }, - "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==", + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "rsvp": "^4.8.4" + "clone": "1.0.4" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "ccount": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", - "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", + "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 }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "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": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "object-keys": "1.1.1" } }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "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": { - "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" + "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.3" + } + }, + "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.3" + } + }, + "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.3" + } + } } }, - "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" - } + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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=" + }, + "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": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, - "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 + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "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==", + "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 }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", "dev": true }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "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==", + "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 }, - "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "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": { - "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" + "acorn-node": "1.8.2", + "defined": "1.0.0", + "minimist": "1.2.5" } }, - "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.13.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.2.tgz", - "integrity": "sha512-zWD9RVVKd8Nx2xKGY4G08lb3nCD+2hmICxovvRE9QjBKQzHFvCYqGlsw15b4zUxLKq3wXEwVbR/yLtMbfk7JbQ==", - "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" + "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.17.4", + "@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.2.1", + "puppeteer-core": "9.1.1", + "query-selector-shadow-dom": "1.0.0", + "ua-parser-js": "0.7.28", + "uuid": "8.3.2" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "@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": { - "glob": "^7.1.3" + "chalk": "4.1.1", + "loglevel": "1.7.1", + "loglevel-plugin-prefix": "0.8.4", + "strip-ansi": "6.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 - }, - "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": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + }, + "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.3.0", + "supports-color": "7.2.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": { - "is-descriptor": "^0.1.0" + "has-flag": "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==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz", - "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==", - "dev": true - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "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 }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "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=", + "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 }, - "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=", + "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 }, - "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==", + "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": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "bn.js": "4.12.0", + "miller-rabin": "4.0.1", + "randombytes": "2.1.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" - } + "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 } } }, - "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 - }, - "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==", - "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" - } + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "esutils": "2.0.3" } }, - "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==", + "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": { - "color-name": "1.1.3" + "esutils": "2.0.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 - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "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 - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "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 - }, - "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 - }, - "compress-commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-3.0.0.tgz", - "integrity": "sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg==", - "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" - } - } - } - }, - "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": { - "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" - } - } - } - }, - "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 - } - } - }, - "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 - }, - "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 - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "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 - }, - "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==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "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.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", - "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" - } - }, - "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==", - "dev": true, - "requires": { - "browserslist": "^4.8.5", - "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 - } - } - }, - "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==" - }, - "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 - }, - "coveralls": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", - "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", - "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" - } - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "requires": { - "buffer": "^5.1.0" - } - }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.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 - } - } - }, - "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" - } - }, - "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": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "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 - } - } - }, - "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==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "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": { - "array-find-index": "^1.0.1" - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "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": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.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==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "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" - } - } - } - }, - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "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" - } - }, - "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" - } - }, - "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": { - "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 - } - } - }, - "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=", - "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==", - "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 - } - } - }, - "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": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "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 - }, - "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 - }, - "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": { - "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 - } - } - }, - "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=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "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, - "optional": true, - "requires": { - "clone": "^1.0.2" - } - }, - "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 - }, - "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==", - "requires": { - "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": { - "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" - } - } - } - }, - "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=" - }, - "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=" - }, - "detab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", - "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", - "dev": true, - "requires": { - "repeat-string": "^1.5.4" - } - }, - "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": "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" - } - }, - "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": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "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" - } - }, - "devtools": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.1.16.tgz", - "integrity": "sha512-Px/K/xYY+fTW8D5yt7p6ZZJfkfHHulKVr2Y+BJSCQyKNSY/hiZFT6KAjoUFrAastLCqqs1gW2Dy/OGb0qWm+Hg==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.14", - "@wdio/utils": "6.1.8", - "chrome-launcher": "^0.13.1", - "puppeteer-core": "^3.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" - } - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "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.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, - "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" - }, - "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 - } - } - }, - "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=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "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=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "documentation": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-5.5.0.tgz", - "integrity": "sha512-Aod3HOI+8zMhwWztDlECRsDfJ8SFu4oADvipOLq3gnWKy4Cpg2oF5AWT+U6PcX85KuguDI6c+q+2YwYEx99B/A==", - "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", - "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" - }, - "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=", - "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" - } - } - } - }, - "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" - } - } - } - }, - "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": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "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 - }, - "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" - }, - "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" - } - } - } - }, - "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": { - "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-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" - } - }, - "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" - } - } - } - }, - "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" - } - }, - "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" - } - }, - "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=", - "dev": true, - "requires": { - "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" - } - }, - "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=", - "dev": true, - "requires": { - "ansi-regex": "^2.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 - }, - "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=", - "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=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "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=", - "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": { - "readable-stream": "^2.0.2" - }, - "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" - } - } - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.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" - } - } - } - }, - "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": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "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": { - "ansi-regex": "^3.0.0", - "wcwidth": ">=1.0.1" - }, - "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 - } - } - }, - "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" - } - }, - "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 - }, - "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.3", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz", - "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==", - "dev": true, - "requires": { - "jake": "^10.6.1" - } - }, - "electron-to-chromium": { - "version": "1.3.458", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.458.tgz", - "integrity": "sha512-OjRkb0igW0oKE2QbzS7vBYrm7xjW/KRTtIj0OGGx57jr/YhBiKb7oZvdbaojqjfCb/7LbnwsbMbdsYjthdJbAw==", - "dev": true - }, - "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", - "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" - }, - "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 - } - } - }, - "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": { - "once": "^1.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==", - "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" + "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.14.7", + "@babel/types": "7.14.5", + "@vue/compiler-sfc": "3.1.2", + "ansi-html": "0.0.7", + "babelify": "10.0.0", + "chalk": "2.4.2", + "chokidar": "3.5.2", + "concat-stream": "1.6.2", + "diff": "4.0.2", + "doctrine-temporary-fork": "2.1.0", + "get-port": "5.1.1", + "git-url-parse": "11.4.4", + "github-slugger": "1.2.0", + "glob": "7.1.7", + "globals-docs": "2.4.1", + "highlight.js": "10.7.3", + "ini": "1.3.8", + "js-yaml": "3.14.1", + "lodash": "4.17.21", + "mdast-util-find-and-replace": "1.1.1", + "mdast-util-inject": "1.1.0", + "micromatch": "3.1.10", + "mime": "2.5.2", + "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.20.0", + "stream-array": "1.1.2", + "strip-json-comments": "2.0.1", + "tiny-lr": "1.1.1", + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.3", + "vfile": "4.2.1", + "vfile-reporter": "6.0.2", + "vfile-sort": "2.2.2", + "vinyl": "2.2.1", + "vinyl-fs": "3.0.3", + "vue-template-compiler": "2.6.14", + "yargs": "15.4.1" }, "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "@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": { - "ms": "2.0.0" + "@babel/code-frame": "7.14.5", + "@babel/generator": "7.12.1", + "@babel/helper-module-transforms": "7.14.5", + "@babel/helpers": "7.14.6", + "@babel/parser": "7.12.3", + "@babel/template": "7.14.5", + "@babel/traverse": "7.14.7", + "@babel/types": "7.14.5", + "convert-source-map": "1.8.0", + "debug": "4.3.1", + "gensync": "1.0.0-beta.2", + "json5": "2.2.0", + "lodash": "4.17.21", + "resolve": "1.20.0", + "semver": "5.7.1", + "source-map": "0.5.7" } }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "@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": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "@babel/types": "7.14.5", + "jsesc": "2.5.2", + "source-map": "0.5.7" } - } - } - }, - "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==", - "dev": 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" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + }, + "@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 }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "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": { - "ms": "2.0.0" + "color-convert": "2.0.1" } }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "sprintf-js": "1.0.3" } - } - } - }, - "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==", - "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" - } - }, - "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" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "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" - } - }, - "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==", - "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" - } - }, - "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==", - "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" - } - }, - "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": { - "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=", + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.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.1" + } + } } - } - } - }, - "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" - } - }, - "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, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "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.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.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==", + }, + "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, - "optional": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "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", - "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" - }, - "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==", + "requires": { + "string-width": "4.2.2", + "strip-ansi": "6.0.0", + "wrap-ansi": "6.2.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 }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "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" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "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=", + "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": { - "restore-cursor": "^2.0.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.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.1" + } + } } }, - "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=", + "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": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "locate-path": "3.0.0" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "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": { - "ms": "^2.1.1" + "kind-of": "3.2.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.6" + } + } } }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "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": { - "esutils": "^2.0.2" + "argparse": "1.0.10", + "esprima": "4.0.1" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "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": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "graceful-fs": "4.2.6", + "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 + } } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "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": { - "escape-string-regexp": "^1.0.5" + "p-locate": "3.0.0", + "path-exists": "3.0.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==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "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" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, - "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 - }, - "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==", + "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": { @@ -8005,696 +6432,495 @@ "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=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "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": { - "mimic-fn": "^1.0.0" + "p-limit": "2.3.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=", + "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": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" } }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "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": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.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 + } } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "load-json-file": "4.0.0", + "normalize-package-data": "2.5.0", + "path-type": "3.0.0" } - } - } - }, - "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.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", - "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": { - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + }, + "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": "^2.1.0" + "find-up": "3.0.0", + "read-pkg": "3.0.0" } - } - } - }, - "eslint-plugin-import": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", - "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.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=", + }, + "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 + }, + "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": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "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": { - "pify": "^2.0.0" + "ansi-styles": "4.3.0", + "string-width": "4.2.2", + "strip-ansi": "6.0.0" } }, - "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" + "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.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "4.2.2", + "which-module": "2.0.0", + "y18n": "4.0.3", + "yargs-parser": "18.1.3" + }, + "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.3.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 + } } }, - "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=", + "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": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "camelcase": "5.3.1", + "decamelize": "1.2.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 - } - } - }, - "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=", - "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.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "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==", - "dev": true } } }, - "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.1", + "ent": "2.2.0", + "extend": "3.0.2", + "void-elements": "2.0.1" } }, - "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", - "dev": true - }, - "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "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.3.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.3.7" }, "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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "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.2" } } } }, - "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": "25.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", - "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { - "@jest/types": "^25.5.0", - "ansi-styles": "^4.0.0", - "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.5.0", - "jest-message-util": "^25.5.0", - "jest-regex-util": "^25.2.6" + "end-of-stream": "1.4.4", + "inherits": "2.0.3", + "readable-stream": "2.3.7", + "stream-shift": "1.0.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==", + "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" - } - }, - "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==", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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": { - "color-name": "~1.1.4" + "safe-buffer": "5.1.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 } } }, - "expect-webdriverio": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-1.1.5.tgz", - "integrity": "sha512-+RL4Lkne+/z+o1G5Y0S5QuEXeICxt4jExhBSM2Jn/mrDwb+PZVKPM2Yd1OzLsKeCdQLtw4Oft6514Gp5GLgdZA==", + "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": "^25.2.1", - "jest-matcher-utils": "^25.1.0" - } - }, - "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.4", + "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 - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "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": { - "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" - } - } + "jsbn": "0.1.1", + "safer-buffer": "2.1.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==", + "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": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "@types/which": "1.3.2", + "which": "2.0.2" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "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": { - "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" + "jake": "10.8.2" + } + }, + "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": { + "bn.js": "4.12.0", + "brorand": "1.1.0", + "hash.js": "1.1.7", + "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.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.0.tgz", - "integrity": "sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==", + "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": { + "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": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" + "accepts": "1.3.7", + "base64id": "2.0.0", + "cookie": "0.4.1", + "cors": "2.8.5", + "debug": "4.3.1", + "engine.io-parser": "4.0.2", + "ws": "7.4.6" }, "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": { @@ -8702,2266 +6928,2622 @@ "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 } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "faker": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-3.1.0.tgz", - "integrity": "sha1-D5CPr05uwCUk5UpX5DLFwBPgjJ8=", - "dev": true - }, - "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==", + "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": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "base64-arraybuffer": "0.1.4" } }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "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.2.6", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.9" + } }, - "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 + "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-colors": "4.1.1" + } }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "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=", + "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": { - "websocket-driver": ">=0.5.1" + "prr": "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==", + "error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", "dev": true, "requires": { - "bser": "2.1.1" + "string-template": "0.2.1" } }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "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": { - "pend": "~1.2.0" + "is-arrayish": "0.2.1" } }, - "fibers": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-4.0.3.tgz", - "integrity": "sha512-MW5VrDtTOLpKK7lzw4qD7Z9tXaAhdOmOED5RHzg3+HjUk+ibkjVW0Py2ERtdqgTXaerLkVkBy2AEmJiT6RMyzg==", + "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": { + "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" + } + }, + "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": { + "call-bind": "1.0.2", + "get-intrinsic": "1.1.1", + "has-symbols": "1.0.2", + "is-arguments": "1.1.0", + "is-map": "2.0.2", + "is-set": "2.0.2", + "is-string": "1.0.6", + "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 + } + } + }, + "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": { - "detect-libc": "^1.0.3" + "is-callable": "1.2.3", + "is-date-object": "1.0.4", + "is-symbol": "1.0.4" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "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": { - "escape-string-regexp": "^1.0.5" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.3", + "next-tick": "1.0.0" } }, - "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=", + "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": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-symbol": "3.1.3" } }, - "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.1", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", - "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "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": { - "minimatch": "^3.0.4" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.3", + "event-emitter": "0.3.5" } }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "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 }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "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": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "es6-promise": "4.2.8" } }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "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==", + "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": { - "to-regex-range": "^5.0.1" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "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.0.1", + "es5-ext": "0.10.53" + } + } } }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "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": { - "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" + "d": "1.0.1", + "ext": "1.4.0" } }, - "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==", + "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, "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.3" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.3", + "optionator": "0.8.3", + "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": "1.0.1" + } + }, + "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" + } + } } }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "es6-map": "0.1.5", + "es6-weak-map": "2.0.3", + "esrecurse": "4.3.0", + "estraverse": "4.3.0" + } + }, + "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": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "0.4.2", + "ajv": "6.12.6", + "chalk": "4.1.1", + "cross-spawn": "7.0.3", + "debug": "4.3.1", + "doctrine": "3.0.0", + "enquirer": "2.3.6", + "escape-string-regexp": "4.0.0", + "eslint-scope": "5.1.1", + "eslint-utils": "2.1.0", + "eslint-visitor-keys": "2.1.0", + "espree": "7.3.1", + "esquery": "1.4.0", + "esutils": "2.0.3", + "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.9.0", + "ignore": "4.0.6", + "import-fresh": "3.3.0", + "imurmurhash": "0.1.4", + "is-glob": "4.0.1", + "js-yaml": "3.14.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.3", + "regexpp": "3.2.0", + "semver": "7.3.5", + "strip-ansi": "6.0.0", + "strip-json-comments": "3.1.1", + "table": "6.7.1", + "text-table": "0.2.0", + "v8-compile-cache": "2.3.0" }, "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "@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": { - "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" - } - } + "@babel/highlight": "7.14.5" } }, - "fill-range": { + "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.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.4.1" + } + }, + "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" + } + }, + "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.3" + } + }, + "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.3.0", + "supports-color": "7.2.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 + }, + "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": { + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.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" + } + }, + "escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "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": { - "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" - } - } + "type-fest": "0.20.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==", + "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-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" - }, - "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" - } - } + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "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" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "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": { - "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" + "lru-cache": "6.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=", + "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": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.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 } } }, - "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==", + "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 }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "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": { - "is-buffer": "~2.0.3" + "debug": "2.6.9", + "resolve": "1.20.0" } }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "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": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "debug": "3.2.7", + "pkg-dir": "2.0.0" }, "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "glob": "^7.1.3" + "ms": "2.1.3" } - } - } - }, - "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==", + }, + "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": { - "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" + "locate-path": "2.0.0" } - } - } - }, - "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + }, + "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": { - "ms": "^2.1.1" + "p-locate": "2.0.0", + "path-exists": "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==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "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" - } - }, - "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==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "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=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "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=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "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==", + }, + "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.3.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": { - "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": "2.1.0" } } } }, - "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.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", - "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-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": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "eslint-utils": "2.1.0", + "regexpp": "3.2.0" } }, - "fs.extra": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", - "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", + "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": { - "fs-extra": "~0.6.1", - "mkdirp": "~0.3.5", - "walk": "^2.3.9" + "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.1.0", + "has": "1.0.3", + "is-core-module": "2.4.0", + "minimatch": "3.0.4", + "object.values": "1.1.4", + "pkg-up": "2.0.0", + "read-pkg-up": "3.0.0", + "resolve": "1.20.0", + "tsconfig-paths": "3.9.0" }, "dependencies": { - "fs-extra": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", - "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "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.3" + } + }, + "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.2.6", + "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": { - "jsonfile": "~1.0.1", - "mkdirp": "0.3.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" + "p-limit": "1.3.0" } }, - "jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "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 }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "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.2", + "json-parse-better-errors": "1.0.2" + } + }, + "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 }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "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.5.0", + "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.1.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.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" - } - }, - "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==" - }, - "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==", + "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": { - "globule": "^1.0.0" + "eslint-plugin-es": "3.0.1", + "eslint-utils": "2.1.0", + "ignore": "5.1.8", + "minimatch": "3.0.4", + "resolve": "1.20.0", + "semver": "6.3.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 + } } }, - "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=", + "eslint-plugin-prebid": { + "version": "file:plugins/eslint", "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=", + "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 }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "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 }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "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": { - "pump": "^3.0.0" + "esrecurse": "4.3.0", + "estraverse": "4.3.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=", + "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": { - "assert-plus": "^1.0.0" + "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 + } } }, - "git-up": { + "eslint-visitor-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-2.1.0.tgz", - "integrity": "sha512-MJgwfcSd9qxgDyEYpRU/CDxNpUadrK80JHuEQDG4Urn0m7tpSOgCBrtiSIa9S9KH8Tbuo/TN8SSQmJBvsw1HkA==", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "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": { - "is-ssh": "^1.3.0", - "parse-url": "^3.0.2" + "acorn": "7.4.1", + "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 + } } }, - "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==", + "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.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { - "git-up": "^2.0.0", - "parse-domain": "^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 + } } }, - "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==", + "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": { - "emoji-regex": ">=6.0.0 <=6.1.1" + "estraverse": "5.2.0" }, "dependencies": { - "emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "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 + }, + "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": { - "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" + "d": "1.0.1", + "es5-ext": "0.10.53" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "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": { - "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=", - "dev": true, - "requires": { - "is-glob": "^2.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=", - "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" - } - } + "duplexer": "0.1.2", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" } }, - "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==", + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "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==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "md5.js": "1.3.5", + "safe-buffer": "5.1.2" } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "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": { - "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" + "cross-spawn": "6.0.5", + "get-stream": "4.1.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.3", + "strip-eof": "1.0.0" }, "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "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": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "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": { - "is-extglob": "^2.1.0" + "pump": "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" - } - } - } - }, - "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", - "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", - "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==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "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": { - "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" - } - } + "shebang-regex": "1.0.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" - }, - "dependencies": { - "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 - } - } + "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 }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "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" - } - } + "isexe": "2.0.0" } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + } + } + }, + "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": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "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, - "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "is-descriptor": "0.1.6" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "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-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" - } - } + "is-extendable": "0.1.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=", + } + } + }, + "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.2.4" + }, + "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": { - "binary-extensions": "^1.0.0" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.1", + "repeat-element": "1.1.4", + "repeat-string": "1.6.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-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" - } - } - } - }, - "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" - } - }, - "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==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "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" + "kind-of": "3.2.2" } }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "isarray": "1.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=", + "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-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-buffer": "1.1.6" } } } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "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": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "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==", - "dev": true - }, - "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", + "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": { - "glob": "~7.1.1", - "lodash": "~4.17.12", - "minimatch": "~3.0.2" + "homedir-polyfill": "1.0.3" } }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "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": { - "sparkles": "^1.0.0" + "@jest/types": "27.0.2", + "ansi-styles": "5.2.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-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 + } } }, - "got": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/got/-/got-11.2.0.tgz", - "integrity": "sha512-68pBow9XXXSdVRV5wSx0kWMCZsag4xE3Ru0URVe0PWsSYmU4SJrUmEO6EVYFlFHc9rq/6Yqn6o1GxIb9torQxg==", + "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": { - "@sindresorhus/is": "^2.1.1", - "@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", - "get-stream": "^5.1.0", - "http2-wrapper": "^1.0.0-beta.4.5", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "expect": "27.0.2", + "jest-matcher-utils": "27.0.2" } }, - "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==", - "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==", - "dev": true + "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.7", + "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" + } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "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.5.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 + } + } }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "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==", + "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": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "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": { - "ansi-wrap": "^0.1.0" + "is-plain-object": "2.0.4" } - }, - "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=", + } + } + }, + "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": { + "chardet": "0.7.0", + "iconv-lite": "0.4.24", + "tmp": "0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "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.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "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": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.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==", - "dev": true - }, - "gulp-cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.1.tgz", - "integrity": "sha512-yEMxrXqY8mJFlaauFQxNrCpzWJThu0sH1sqlToaTOT063Hub9s/Nt2C+GSLe6feQ/IMWrHvGOOsyES7CQc9O+A==", - "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.1.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.0.1", - "yargs": "^7.1.0" + "is-descriptor": "1.0.2" } }, - "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=", + "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": { - "number-is-nan": "^1.0.0" + "is-extendable": "0.1.1" } }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "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": { - "lcid": "^1.0.0" + "kind-of": "6.0.3" } }, - "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=", + "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": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "kind-of": "6.0.3" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "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": { - "ansi-regex": "^2.0.0" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } - }, - "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=", + } + } + }, + "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": { + "@types/yauzl": "2.9.1", + "debug": "4.3.1", + "get-stream": "5.2.0", + "yauzl": "2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ms": "2.1.2" } }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "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 - }, - "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==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } } } }, - "gulp-clean": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "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 + }, + "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.1", + "time-stamp": "1.1.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.7.4" + } + }, + "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": { + "flat-cache": "3.0.4" + } + }, + "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": { + "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 + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "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==", + "dev": true, + "requires": { + "to-regex-range": "5.0.1" + } + }, + "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": { + "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" + } + }, + "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": { + "commondir": "1.0.1", + "make-dir": "3.1.0", + "pkg-dir": "4.2.0" + } + }, + "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": { - "gulp-util": "^2.2.14", - "rimraf": "^2.2.8", - "through2": "^0.4.2" + "detect-file": "1.0.0", + "is-glob": "4.0.1", + "micromatch": "3.1.10", + "resolve-dir": "1.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=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "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" + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "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": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" + "is-extendable": "0.1.1" } } } }, - "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" - } - }, - "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=", - "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 - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "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": { - "ansi-regex": "^0.2.1" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.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.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=", + "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": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" + "kind-of": "3.2.2" }, "dependencies": { - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "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": { - "object-keys": "~0.4.0" + "is-buffer": "1.1.6" } } } }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "clone-stats": "~0.0.1" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "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" + } + } + } + }, + "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.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" + } + }, + "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": "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": { + "flatted": "3.1.1", + "rimraf": "3.0.2" + }, + "dependencies": { + "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.7" + } } } }, - "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": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - } + "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 }, - "gulp-connect": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", - "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", + "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": { - "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" + "inherits": "2.0.3", + "readable-stream": "2.3.7" }, "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=", + "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": { - "depd": "~1.1.2", + "core-util-is": "1.0.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", - "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "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": { - "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" + "safe-buffer": "5.1.2" } - }, - "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==", + "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": { - "eslint": "^4.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.0" + "for-in": "1.0.2" } }, - "gulp-footer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.0.2.tgz", - "integrity": "sha512-HsG5VOgKHFRqZXnHGI6oGhPDg70p9pobM+dYOnjBZVLMQUHzLG6bfaPNRJ7XG707E+vWO3TfN0CND9UrYhk94g==", + "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==", "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" + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.31" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "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": { + "map-cache": "0.2.2" + } + }, + "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 + }, + "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": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dev": true, + "requires": { + "graceful-fs": "4.2.6", + "jsonfile": "6.1.0", + "universalify": "2.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=", + "dev": true, + "requires": { + "graceful-fs": "4.2.6", + "through2": "2.0.5" }, "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=", - "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=", + "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": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "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": { - "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" + "safe-buffer": "5.1.2" } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "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": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "readable-stream": "2.3.7", + "xtend": "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=", - "dev": true } } }, - "gulp-header": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", - "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "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": { - "concat-with-sourcemaps": "*", - "lodash.template": "^4.4.0", - "through2": "^2.0.0" + "fs-extra": "0.6.4", + "mkdirp": "0.3.5", + "walk": "2.3.14" }, "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==", + "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": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" + "jsonfile": "1.0.1", + "mkdirp": "0.3.5", + "ncp": "0.4.2", + "rimraf": "2.2.8" } }, - "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" - } + "jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "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 + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true } } }, - "gulp-if": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", + "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, - "requires": { - "gulp-match": "^1.0.3", - "ternary-stream": "^2.0.1", - "through2": "^2.0.1" - } + "optional": true }, - "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=", + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, "requires": { - "through2": "^0.6.3" + "graceful-fs": "4.2.6", + "inherits": "2.0.3", + "mkdirp": "0.5.5", + "rimraf": "2.5.4" }, "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=", - "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.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "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": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "minimist": "1.2.5" } } } }, - "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==", + "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": { - "minimatch": "^3.0.3" + "globule": "1.3.2" } }, - "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==", + "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": { - "istextorbinary": "2.2.1", - "readable-stream": "^2.0.1", - "replacestream": "^4.0.0" + "loader-utils": "1.4.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==", + "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.5" + } + }, + "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": { - "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" + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.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, - "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 - } - } + "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 }, - "gulp-sourcemaps": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", - "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 - } - } + "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 }, - "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==", + "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-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": { - "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" + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.2" } }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "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": { - "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" + "hosted-git-info": "2.8.9", + "meow": "3.7.0", + "normalize-package-data": "2.5.0", + "parse-github-repo-url": "1.4.1", + "through2": "2.0.5" }, "dependencies": { - "ansi-regex": { + "camelcase": { "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=", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "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": { - "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" + "camelcase": "2.1.1", + "map-obj": "1.0.1" } }, - "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=", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "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=", + "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": { - "lodash._root": "^3.0.0" + "repeating": "2.0.1" } }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.5", + "normalize-package-data": "2.5.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" } }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "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": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "indent-string": "2.1.0", + "strip-indent": "1.0.1" } }, - "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 - }, - "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=", + "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": { - "ansi-regex": "^2.0.0" + "safe-buffer": "5.1.2" } }, - "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 + "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" + } }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "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": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true } } }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "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.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": { + "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-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": { - "glogg": "^1.0.0" + "dargs": "7.0.0", + "lodash": "4.17.21", + "meow": "8.1.2", + "split2": "3.2.2", + "through2": "4.0.2" } }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "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": { - "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 - } + "gitconfiglocal": "1.0.0", + "pify": "2.3.0" } }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "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": { - "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 - } + "meow": "8.1.2", + "semver": "6.3.0" } }, - "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.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "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": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "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" - } - } + "is-ssh": "1.3.3", + "parse-url": "5.0.5" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "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": { - "function-bind": "^1.1.1" + "git-up": "4.0.2" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", "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 - } + "ini": "1.3.8" } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "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": { - "isarray": "2.0.1" + "emoji-regex": "6.1.1" }, "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "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 } } }, - "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 - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "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": { - "sparkles": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, - "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 - }, - "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==" - }, - "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==", + "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": { - "has-symbol-support-x": "^1.4.1" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "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": { + "is-glob": "2.0.1" + } + }, + "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" + } + } } }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "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": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "is-glob": "4.0.1" } }, - "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=", + "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": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "extend": "3.0.2", + "glob": "7.1.7", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.5.1", + "readable-stream": "2.3.7", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.3.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==", + "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.2" + } + }, + "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.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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + } + } + }, + "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.3.2", + "chokidar": "2.1.8", + "is-negated-glob": "1.0.0", + "just-debounce": "1.1.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==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "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.1.0" + } + } + } + }, + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.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.1" + } + } + } + }, + "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.3", + "braces": "2.3.2", + "fsevents": "1.2.13", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.2.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.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.1" + } + } + } + }, + "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": { + "bindings": "1.5.0", + "nan": "2.14.2" + } + }, + "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.2" + }, + "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.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=", + "dev": true, + "requires": { + "binary-extensions": "1.13.1" + } + }, "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" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -10970,1204 +9552,1594 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } - } - } - }, - "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": { - "inherits": "^2.0.4", - "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 + "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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "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.2.6", + "micromatch": "3.1.10", + "readable-stream": "2.3.7" + } + }, + "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.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=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } } } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" } }, - "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==", + "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": { - "xtend": "^4.0.1" - } - }, - "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=", - "dev": 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" + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.3", + "ini": "1.3.8", + "is-windows": "1.0.2", + "which": "1.3.1" }, "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 + "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" + } } } }, - "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 - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "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 }, - "highlight.js": { - "version": "9.18.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", - "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==", + "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 }, - "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=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^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==", + "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": { - "parse-passwd": "^1.0.0" + "glob": "7.1.7", + "lodash": "4.17.21", + "minimatch": "3.0.4" } }, - "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.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "html-encoding-sniffer": { + "glogg": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "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-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-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 - }, - "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.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", - "dev": true - }, - "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": { - "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.0-beta.4.6", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.4.6.tgz", - "integrity": "sha512-9oB4BiGDTI1FmIBlOF9OJ5hwJvcBEmPCqk/hy314Uhy2uq5TjekUZM8w8SPLLlUEM+mxNhXdPAXfrJN2Zbb/GQ==", - "dev": true, - "requires": { - "quick-lru": "^5.0.0", - "resolve-alpn": "^1.0.0" - } - }, - "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==", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", "dev": true, "requires": { - "agent-base": "5", - "debug": "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" - } - }, - "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 - } - } - }, - "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" + "sparkles": "1.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 - }, - "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==", + "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": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "@sindresorhus/is": "4.0.1", + "@szmarczak/http-timer": "4.0.5", + "@types/cacheable-request": "6.0.1", + "@types/responselike": "1.0.0", + "cacheable-lookup": "5.0.4", + "cacheable-request": "7.0.2", + "decompress-response": "6.0.0", + "http2-wrapper": "1.0.3", + "lowercase-keys": "2.0.0", + "p-cancelable": "2.1.1", + "responselike": "2.0.0" } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "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 }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "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 }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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": { - "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.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "glob-watcher": "5.0.5", + "gulp-cli": "2.3.0", + "undertaker": "1.3.0", + "vinyl-fs": "3.0.3" }, "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==", + "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": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "ansi-wrap": "0.1.0" } }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.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==", + "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.1.0", + "archy": "1.0.0", + "array-sort": "1.0.0", + "color-support": "1.1.3", + "concat-stream": "1.6.2", + "copy-props": "2.0.5", + "fancy-log": "1.3.3", + "gulplog": "1.0.0", + "interpret": "1.4.0", + "isobject": "3.0.1", + "liftoff": "3.1.0", + "matchdep": "2.0.0", + "mute-stdout": "1.0.1", + "pretty-hrtime": "1.0.3", + "replace-homedir": "1.0.0", + "semver-greatest-satisfied-range": "1.1.0", + "v8flags": "3.2.0", + "yargs": "7.1.2" + } + }, + "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": { - "color-name": "~1.1.4" + "number-is-nan": "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==", + "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 }, - "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-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.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "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.1.1" + } + }, + "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 }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "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.2", + "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.2.0", + "get-caller-file": "1.0.3", + "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.2", + "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": { - "has-flag": "^4.0.0" + "camelcase": "3.0.0", + "object.assign": "4.1.2" } } } }, - "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=", + "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": { - "kind-of": "^3.0.2" + "gulp-util": "2.2.20", + "rimraf": "2.5.4", + "through2": "0.4.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==", + "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.1.1", + "map-obj": "1.0.1" + } + }, + "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.5", + "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.7.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.1", + "dateformat": "1.0.12", + "lodash._reinterpolate": "2.4.1", + "lodash.template": "2.4.1", + "minimist": "0.2.1", + "multipipe": "0.1.2", + "through2": "0.5.1", + "vinyl": "0.2.3" + }, + "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.34", + "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.1" + } + }, + "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.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.5", + "normalize-package-data": "2.5.0", + "object-assign": "4.1.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 + } } - } - } - }, - "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==" - }, - "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-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-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==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.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=", - "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==", + }, + "minimist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", + "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", "dev": true }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "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": { - "is-buffer": "^1.1.5" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" } - } - } - }, - "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==" - }, - "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==", + }, + "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 - } - } - }, - "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-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-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-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=", - "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-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "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" + }, + "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.34", + "xtend": "2.1.2" + }, + "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 + } } }, - "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-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==", + "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": { - "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.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "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==", - "requires": { - "has-symbols": "^1.0.1" + "concat-with-sourcemaps": "1.1.0", + "through2": "2.0.5", + "vinyl": "2.2.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==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" + } + } } }, - "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==", + "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": { - "unc-path-regex": "^0.1.2" + "ansi-colors": "2.0.5", + "connect": "3.7.0", + "connect-livereload": "0.6.1", + "fancy-log": "1.3.3", + "map-stream": "0.0.7", + "send": "0.16.2", + "serve-index": "1.9.1", + "serve-static": "1.14.1", + "tiny-lr": "1.1.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" + } + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "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.3", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.1", + "statuses": "1.4.0" + } + }, + "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 + } } }, - "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-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-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": { - "is-docker": "^2.0.0" + "eslint": "4.19.1", + "fancy-log": "1.3.3", + "plugin-error": "1.0.1" }, "dependencies": { - "is-docker": { + "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-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "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 + } + } + }, + "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-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": { + "sprintf-js": "1.0.3" + } + }, + "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": { + "restore-cursor": "2.0.0" + } + }, + "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 + }, + "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.1.5", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "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.3" + } + }, + "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.3" + } + }, + "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.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.4.2", + "concat-stream": "1.6.2", + "cross-spawn": "5.1.0", + "debug": "3.2.7", + "doctrine": "2.1.0", + "eslint-scope": "3.7.3", + "eslint-visitor-keys": "1.3.0", + "espree": "3.5.4", + "esquery": "1.4.0", + "esutils": "2.0.3", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.7", + "globals": "11.12.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.14.1", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.21", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "natural-compare": "1.4.0", + "optionator": "0.8.3", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.3", + "regexpp": "1.1.0", + "require-uncached": "1.0.3", + "semver": "5.7.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + } + }, + "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.3.0", + "estraverse": "4.3.0" + } + }, + "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.7.4", + "acorn-jsx": "3.0.1" + } + }, + "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": { + "chardet": "0.4.2", + "iconv-lite": "0.4.24", + "tmp": "0.0.33" + } + }, + "figures": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "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": { + "flat-cache": "1.3.4", + "object-assign": "4.1.1" + } + }, + "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.3", + "graceful-fs": "4.2.6", + "rimraf": "2.6.3", + "write": "0.2.1" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "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==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } - }, - "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": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + }, + "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.2.0", + "chalk": "2.4.2", + "cli-cursor": "2.1.0", + "cli-width": "2.2.1", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.21", + "mute-stream": "0.0.7", + "run-async": "2.4.1", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "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 }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "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": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "argparse": "1.0.10", + "esprima": "4.0.1" } }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "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" + } + }, + "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 }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "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" + } + }, + "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 }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "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": { + "mimic-fn": "1.2.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" + } + }, + "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 + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "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": { + "onetime": "2.0.1", + "signal-exit": "3.0.3" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "7.1.7" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "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 + }, + "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": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "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" + } + }, + "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-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": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.4.2", + "lodash": "4.17.21", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + } + }, + "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" + } + }, + "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" + } + } + } + }, + "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": { + "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": { + "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": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "lodash._root": "3.0.1" } }, - "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 - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "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.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "dev": true, - "optional": true, "requires": { - "amdefine": ">=0.0.4" + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "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": { - "has-flag": "^1.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.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 } } }, - "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" + "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": { + "concat-with-sourcemaps": "1.1.0", + "lodash.template": "4.5.0", + "map-stream": "0.0.7", + "through2": "2.0.5" }, "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "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": { - "lodash": "^4.17.14" + "lodash._reinterpolate": "3.0.0", + "lodash.templatesettings": "4.2.0" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "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": { - "ms": "^2.1.1" + "lodash._reinterpolate": "3.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=", - "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==", + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", "dev": true }, - "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==", + "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-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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "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==", + "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": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "safe-buffer": "5.1.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==", + "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": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } + } + } + }, + "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": { + "gulp-match": "1.1.0", + "ternary-stream": "3.0.0", + "through2": "3.0.2" + }, + "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 }, - "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==", + "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": { - "handlebars": "^4.0.3" + "inherits": "2.0.4", + "readable-stream": "3.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==", + } + } + }, + "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": { + "through2": "0.6.5" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "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": { - "glob": "^7.1.3" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "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": { - "has-flag": "^1.0.0" + "readable-stream": "1.0.34", + "xtend": "4.0.2" } } } }, - "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==", + "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": { - "append-transform": "^0.4.0" + "minimatch": "3.0.4" } }, - "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==", + "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": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "@types/node": "14.17.4", + "@types/vinyl": "2.0.4", + "istextorbinary": "3.3.0", + "replacestream": "4.0.3", + "yargs-parser": "20.2.9" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "@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 } } }, - "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==", + "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": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" + "chalk": "3.0.0", + "fancy-log": "1.3.3", + "lodash.template": "4.5.0", + "plugin-error": "1.0.1", + "through2": "3.0.2", + "tslib": "1.14.1" }, "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==", + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.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 }, - "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==", + "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": { - "has-flag": "^3.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.templatesettings": "4.2.0" } - } - } - }, - "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==", + }, + "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": { - "ms": "^2.1.1" + "lodash._reinterpolate": "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==", + "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" + } + }, + "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": "3.6.0" + } + } + } + }, + "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.2", + "convert-source-map": "1.8.0", + "css": "3.0.0", + "debug-fabulous": "1.1.0", + "detect-newline": "2.1.0", + "graceful-fs": "4.2.6", + "source-map": "0.6.1", + "strip-bom-string": "1.0.0", + "through2": "2.0.5" + }, + "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 }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "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 }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "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": { - "glob": "^7.1.3" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "source-map": { @@ -12175,384 +11147,395 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "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.2" + } + }, + "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.7", + "xtend": "4.0.2" + } } } }, - "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" - } - }, - "jake": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.1.tgz", - "integrity": "sha512-eSp5h9S7UFzKdQERTyF+KuPLjDZa1Tbw8gCVUn98n4PbIkLEDGe4zl7vF4Qge9kQj06HcymnksPk8jznPZeKsA==", + "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": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "plugin-error": "1.0.1", + "terser": "5.4.0", + "through2": "4.0.2", + "vinyl-sourcemaps-apply": "0.2.1" } }, - "jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", - "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "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": { - "import-local": "^2.0.0", - "jest-cli": "^24.9.0" + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.3", + "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.6.2", + "minimist": "1.2.5", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.5", + "vinyl": "0.5.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==", - "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==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "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": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.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==", + "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 }, - "find-up": { + "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/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "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": { - "locate-path": "^3.0.0" + "lodash._root": "3.0.1" } }, - "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 - }, - "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==", + "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": { - "@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" + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "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": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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": { - "p-try": "^2.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" } }, - "p-locate": { + "object-assign": { "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==", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "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": { - "p-limit": "^2.0.0" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, - "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==", + "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-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "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": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "safe-buffer": "5.1.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==", + "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": "^4.1.0" + "ansi-regex": "2.1.1" } }, - "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==", + "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 + }, + "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": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "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" + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + } + } + }, + "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.2" + } + }, + "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.2", + "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.2", + "source-map": "0.6.1", + "uglify-js": "3.13.9", + "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.6", + "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": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "fast-deep-equal": "3.1.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.4.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==", + "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.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 + } + } + }, + "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-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.1" + } + }, + "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": { - "@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==", - "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": "*" - } - } + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" } }, - "jest-config": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", - "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", - "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" + "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": { - "@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 - }, - "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" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -12561,1975 +11544,1332 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } }, - "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==", - "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" - } - }, - "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.6" } } } }, - "jest-diff": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", - "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "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": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" + "inherits": "2.0.4", + "readable-stream": "3.6.0", + "safe-buffer": "5.2.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" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "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==", + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "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==", + "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 - }, - "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" - } } } }, - "jest-docblock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", - "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "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": { + "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.2" + } + }, + "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.1.0", + "comma-separated-tokens": "1.0.8", + "hast-util-is-element": "1.1.0", + "hast-util-whitespace": "1.0.4", + "html-void-elements": "1.0.5", + "property-information": "5.6.0", + "space-separated-tokens": "1.1.5", + "stringify-entities": "3.1.0", + "unist-util-is": "4.1.0", + "xtend": "4.0.2" + } + }, + "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 + }, + "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.1.7", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "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==", + "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-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-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 + }, + "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", + "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 + }, + "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": { - "detect-newline": "^2.1.0" + "eventemitter3": "4.0.7", + "follow-redirects": "1.14.1", + "requires-port": "1.0.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==", + "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": { - "@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" - }, - "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==", - "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" - } - } + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" } }, - "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==", + "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": { - "@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" - }, - "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": "*" - } - } + "quick-lru": "5.1.1", + "resolve-alpn": "1.1.2" } }, - "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==", + "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/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" + "agent-base": "5.1.1", + "debug": "4.3.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==", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "ms": "2.1.2" } }, - "@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": "*" - } + "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-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "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==", - "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" + "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" + } + }, + "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 + }, + "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 + }, + "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": { + "parent-module": "1.0.1", + "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": "*" - } - }, - "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": { + "resolve-from": { "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" - } - } - } - }, - "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": { - "bindings": "^1.5.0", - "nan": "^2.12.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==", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "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" - } - } - } - }, - "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" - } - }, - "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" - } - }, - "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-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==", - "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" - }, - "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 - }, - "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==", - "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=", + "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.4.0", + "wrappy": "1.0.2" + } + }, + "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 + }, + "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.3.2", + "chalk": "4.1.1", + "cli-cursor": "3.1.0", + "cli-width": "3.0.0", + "external-editor": "3.1.0", + "figures": "3.2.0", + "lodash": "4.17.21", + "mute-stream": "0.0.8", + "ora": "5.4.1", + "run-async": "2.4.1", + "rxjs": "6.6.7", + "string-width": "4.2.2", + "strip-ansi": "6.0.0", + "through": "2.3.8" + }, + "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": { - "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" - } - } + "color-convert": "2.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-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "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" - } - } + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "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": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "color-name": "1.1.4" } }, - "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==", + "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-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==", + "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==", + "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": { - "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" + "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==", + } + } + }, + "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.2" + } + }, + "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.2.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": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "is-buffer": "1.1.6" } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "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=", + } + } + }, + "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.4", + "is-decimal": "1.0.4" + } + }, + "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.2" + } + }, + "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.2.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.2.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-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-buffer": "1.1.6" } } } }, - "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==", + "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.1.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-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-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": { - "jest-get-type": "^24.9.0", - "pretty-format": "^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": "*" - } - }, - "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==", - "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" - } - } + "isobject": "3.0.1" } }, - "jest-matcher-utils": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", - "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "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-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": { - "chalk": "^3.0.0", - "jest-diff": "^25.5.0", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.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": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "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" - } - } + "call-bind": "1.0.2", + "has-symbols": "1.0.2" } }, - "jest-message-util": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", - "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.5.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^3.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "slash": "^3.0.0", - "stack-utils": "^1.0.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" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "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" - } - } + "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.4.8" } }, - "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==", + "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": { - "@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": "*" - } - } + "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.9.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.4", + "call-bind": "1.0.2", + "es-abstract": "1.18.3", + "foreach": "2.0.5", + "has-symbols": "1.0.2" + } + }, + "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" } }, - "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "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 }, - "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "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 }, - "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==", - "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": "*" - } - } - } + "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 }, - "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==", + "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": { - "@jest/types": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-snapshot": "^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": "*" - } - }, - "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 - } + "is-docker": "2.2.1" } }, - "jest-runner": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", - "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", - "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" - }, - "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" - } - }, - "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" - } - } - } + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, - "jest-runtime": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", - "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", - "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" - }, - "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 - }, - "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" - } - } - } - }, - "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 - }, - "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" - } - } - } - }, - "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==", + "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.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.7.7", + "js-yaml": "3.14.1", + "mkdirp": "0.5.5", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.1", + "wordwrap": "1.0.0" + }, + "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": { - "@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" + "sprintf-js": "1.0.3" } }, - "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==", + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "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==", + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "p-limit": "^2.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, - "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 - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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 }, - "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==", + "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": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "argparse": "1.0.10", + "esprima": "4.0.1" + }, + "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 + } } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "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": { - "ansi-regex": "^4.1.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=", + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "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": "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==", + "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": { - "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" + "has-flag": "1.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==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "isexe": "2.0.0" } } } }, - "jest-serializer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "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 }, - "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==", + "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.14.6", + "@istanbuljs/schema": "0.1.3", + "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": { - "@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" + "istanbul-lib-coverage": "3.0.0", + "make-dir": "3.1.0", + "supports-color": "7.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==", - "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==", - "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": { + "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" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "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": { - "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" - } - } + "has-flag": "4.0.0" } - }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + } + } + }, + "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.3.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": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "ms": "2.1.2" } }, - "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==", + "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-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==", + "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 - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + } + } + }, + "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.2", + "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.3.0", + "textextensions": "3.3.0" + } + }, + "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.2", + "chalk": "2.4.2", + "filelist": "1.0.2", + "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": { + "chalk": "4.1.1", + "diff-sequences": "27.0.1", + "jest-get-type": "27.0.1", + "pretty-format": "27.0.2" + }, + "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": { - "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" + "ansi-styles": "4.3.0", + "supports-color": "7.2.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==", + "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": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "color-name": "1.1.4" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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 }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "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 }, - "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=", + "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": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "has-flag": "4.0.0" } } } }, - "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==", - "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" + "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": { + "chalk": "4.1.1", + "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==", - "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==", + "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" } }, - "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 - } - } - }, - "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==", - "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" - }, - "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==", + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" } }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "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": { - "@types/yargs-parser": "*" + "color-name": "1.1.4" } }, - "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-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==", - "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" + "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": { + "@babel/code-frame": "7.14.5", + "@jest/types": "27.0.2", + "@types/stack-utils": "2.0.0", + "chalk": "4.1.1", + "graceful-fs": "4.2.6", + "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.3.0", + "supports-color": "7.2.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 + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14537,13 +12877,12 @@ "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" } }, "jsbn": { @@ -14552,56 +12891,6 @@ "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" - }, - "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" - } - } - } - }, - "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", @@ -14626,6 +12915,12 @@ "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-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -14651,22 +12946,22 @@ "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" + "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" + "graceful-fs": "4.2.6", + "universalify": "2.0.0" } }, "jsonparse": { @@ -14693,64 +12988,61 @@ "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==", - "dev": true, - "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.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", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" + "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": { + "body-parser": "1.19.0", + "braces": "3.0.2", + "chokidar": "3.5.2", + "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.2", + "source-map": "0.6.1", + "tmp": "0.2.1", + "ua-parser-js": "0.7.28", + "yargs": "16.2.0" }, "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" + "glob": "7.1.7" } }, "source-map": { @@ -14758,17 +13050,38 @@ "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.2" + } + }, + "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.4", + "escalade": "3.1.1", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "string-width": "4.2.2", + "y18n": "5.0.8", + "yargs-parser": "20.2.9" + } } } }, "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=", - "dev": true, - "requires": { - "babel-core": "^6.0.0" - } + "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 }, "karma-browserstack-launcher": { "version": "1.4.0", @@ -14776,9 +13089,9 @@ "integrity": "sha512-bUQK84U+euDfOUfEjcF4IareySMOBNRLrrl9q6cttIe8f011Ir6olLITTYMOJDcGY58wiFIdhPHSPd9Pi6+NfQ==", "dev": true, "requires": { - "browserstack": "~1.5.1", - "browserstacktunnel-wrapper": "~2.0.2", - "q": "~1.5.0" + "browserstack": "1.5.3", + "browserstacktunnel-wrapper": "2.0.4", + "q": "1.5.1" } }, "karma-chai": { @@ -14788,83 +13101,90 @@ "dev": true }, "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" + "which": "1.3.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.3", + "istanbul-lib-report": "3.0.0", + "istanbul-lib-source-maps": "4.0.0", + "istanbul-reports": "3.0.2", + "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-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" + "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==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "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==", + "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": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "debug": "4.3.1", + "istanbul-lib-coverage": "2.0.5", + "make-dir": "2.1.0", + "rimraf": "2.7.1", + "source-map": "0.6.1" + }, + "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.7.1" } }, "ms": { @@ -14873,10 +13193,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.7" + } + }, "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": { @@ -14884,44 +13219,26 @@ "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", "integrity": "sha1-zdADM8znfC5M4D46yT8vjs0fuVI=", "dev": true, "requires": { - "es5-shim": "^4.0.5" + "es5-shim": "4.5.15" } }, "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.2" } }, "karma-ie-launcher": { @@ -14930,24 +13247,16 @@ "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", "dev": true, "requires": { - "lodash": "^4.6.1" + "lodash": "4.17.21" } }, "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.5" } }, "karma-mocha-reporter": { @@ -14956,9 +13265,9 @@ "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", "dev": true, "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" + "chalk": "2.4.2", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0" }, "dependencies": { "ansi-regex": { @@ -14973,7 +13282,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" } }, "strip-ansi": { @@ -14982,7 +13291,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -15012,21 +13321,21 @@ "dev": true }, "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" + "graceful-fs": "4.2.6" } }, "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" + "colors": "1.4.0" } }, "karma-webpack": { @@ -15035,12 +13344,12 @@ "integrity": "sha512-nRudGJWstvVuA6Tbju9tyGUfXTtI1UXMXoRHVmM2/78D0q6s/Ye2IC157PKNDC15PWFGR0mVIRtWLAdcfsRJoA==", "dev": true, "requires": { - "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" + "async": "2.6.3", + "babel-runtime": "6.26.0", + "loader-utils": "1.4.0", + "lodash": "4.17.21", + "source-map": "0.5.7", + "webpack-dev-middleware": "2.0.6" }, "dependencies": { "async": { @@ -15049,21 +13358,35 @@ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "^4.17.14" + "lodash": "4.17.21" + } + }, + "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.5" + } + }, + "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" } } } }, - "kebab-case": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.0.tgz", - "integrity": "sha1-P55JkK3K0MaGwOcB92RYaPdfkes=", - "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" @@ -15075,11 +13398,15 @@ "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 + "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.14.7", + "@babel/traverse": "7.14.7" + } }, "last-run": { "version": "1.1.1", @@ -15087,8 +13414,8 @@ "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", "dev": true, "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" + "default-resolution": "2.0.0", + "es6-weak-map": "2.0.3" } }, "lazy-cache": { @@ -15103,7 +13430,7 @@ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -15112,13 +13439,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -15129,7 +13465,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "1.0.0" } }, "lcov-parse": { @@ -15144,38 +13480,17 @@ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "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==", - "dev": true, - "requires": { - "leven": "^3.1.0" + "flush-write-stream": "1.1.1" } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "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": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.2.1", + "type-check": "0.4.0" } }, "liftoff": { @@ -15184,14 +13499,14 @@ "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", "dev": true, "requires": { - "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" + "extend": "3.0.2", + "findup-sync": "3.0.0", + "fined": "1.2.0", + "flagged-respawn": "1.0.1", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.20.0" } }, "lighthouse-logger": { @@ -15200,10 +13515,16 @@ "integrity": "sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==", "dev": true, "requires": { - "debug": "^2.6.8", - "marky": "^1.2.0" + "debug": "2.6.9", + "marky": "1.2.2" } }, + "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 + }, "listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -15211,13 +13532,10 @@ "dev": true }, "live-connect-js": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.10.tgz", - "integrity": "sha512-G/LJKN3b21DZILCQRyataC/znLvJRyogtu7mAkKlkhP9B9UJ8bcOL7ihW/clD2PsT4hVUkeabHhUGsPCmhsjFw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", + "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", "requires": { - "@kiosked/ulid": "^3.0.0", - "abab": "^2.0.3", - "browser-cookies": "^1.2.0", "tiny-hashes": "1.0.1" } }, @@ -15233,11 +13551,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "graceful-fs": "4.2.6", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" } }, "loader-runner": { @@ -15247,41 +13565,29 @@ "dev": true }, "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==", + "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, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "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" - } - } + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "2.2.0" } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "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": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "4.1.0" } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash._basecopy": { @@ -15308,7 +13614,7 @@ "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", "dev": true, "requires": { - "lodash._htmlescapes": "~2.4.1" + "lodash._htmlescapes": "2.4.1" } }, "lodash._escapestringchar": { @@ -15371,8 +13677,8 @@ "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", "dev": true, "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._htmlescapes": "2.4.1", + "lodash.keys": "2.4.1" } }, "lodash._root": { @@ -15387,9 +13693,16 @@ "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1" + "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", @@ -15402,6 +13715,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", @@ -15420,9 +13739,9 @@ "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", "dev": true, "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._escapehtmlchar": "2.4.1", + "lodash._reunescapedhtml": "2.4.1", + "lodash.keys": "2.4.1" } }, "lodash.flatten": { @@ -15455,6 +13774,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", @@ -15473,9 +13798,9 @@ "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" }, "dependencies": { "lodash.isobject": { @@ -15484,7 +13809,7 @@ "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1" + "lodash._objecttypes": "2.4.1" } } } @@ -15513,25 +13838,19 @@ "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", "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", "dev": true, "requires": { - "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" + "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" }, "dependencies": { "lodash.defaults": { @@ -15540,8 +13859,8 @@ "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._objecttypes": "2.4.1", + "lodash.keys": "2.4.1" } } } @@ -15552,10 +13871,16 @@ "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", "dev": true, "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" + "lodash._reinterpolate": "2.4.1", + "lodash.escape": "2.4.1" } }, + "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", @@ -15568,7 +13893,7 @@ "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", "dev": true, "requires": { - "lodash.keys": "~2.4.1" + "lodash.keys": "2.4.1" } }, "lodash.zip": { @@ -15584,48 +13909,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==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "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": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "chalk": "4.1.1", + "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.3.0", + "supports-color": "7.2.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", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "date-format": "3.0.0", + "debug": "4.3.1", + "flatted": "2.0.2", + "rfdc": "1.3.0", + "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", @@ -15635,9 +14006,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": { @@ -15652,8 +14023,8 @@ "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", "dev": true, "requires": { - "es6-symbol": "^3.1.1", - "object.assign": "^4.1.0" + "es6-symbol": "3.1.3", + "object.assign": "4.1.2" } }, "lolex": { @@ -15674,23 +14045,14 @@ "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", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.3" } }, "lowercase-keys": { @@ -15705,8 +14067,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "lru-queue": { @@ -15715,40 +14077,26 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "es5-ext": "0.10.53" } }, - "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.8" } }, - "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.3.0" } }, "make-iterator": { @@ -15757,16 +14105,7 @@ "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { - "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" + "kind-of": "6.0.3" } }, "map-cache": { @@ -15793,25 +14132,22 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, - "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-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true + "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.6.1" + } }, "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": { @@ -15820,9 +14156,9 @@ "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", "dev": true, "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", + "findup-sync": "2.0.0", + "micromatch": "3.1.10", + "resolve": "1.20.0", "stack-trace": "0.0.10" }, "dependencies": { @@ -15832,16 +14168,16 @@ "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" + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -15850,7 +14186,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -15861,10 +14197,10 @@ "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" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -15873,7 +14209,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -15884,25 +14220,19 @@ "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "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", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } }, "is-number": { @@ -15911,7 +14241,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -15920,7 +14250,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -15931,19 +14261,19 @@ "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" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "to-regex-range": { @@ -15952,8 +14282,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } } } @@ -15970,27 +14300,110 @@ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "hash-base": "3.1.0", + "inherits": "2.0.3", + "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.3" } }, - "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.1.0", + "unist-util-visit-parents": "3.1.1" + }, + "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.3", + "mdast-util-to-string": "2.0.0", + "micromark": "2.11.4", + "parse-entities": "2.0.0", + "unist-util-stringify-position": "2.0.3" + }, + "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.3", + "mdast-util-gfm-strikethrough": "0.2.3", + "mdast-util-gfm-table": "0.1.6", + "mdast-util-gfm-task-list-item": "0.1.6", + "mdast-util-to-markdown": "0.6.5" + } + }, + "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.1.0", + "mdast-util-find-and-replace": "1.1.1", + "micromark": "2.11.4" + } + }, + "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.5" + } + }, + "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.5" + } + }, + "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.5" } }, "mdast-util-inject": { @@ -15999,26 +14412,45 @@ "integrity": "sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=", "dev": true, "requires": { - "mdast-util-to-string": "^1.0.0" + "mdast-util-to-string": "1.1.0" } }, "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": { + "@types/mdast": "3.0.3", + "@types/unist": "2.0.3", + "mdast-util-definitions": "4.0.0", + "mdurl": "1.0.1", + "unist-builder": "2.0.3", + "unist-util-generated": "1.1.6", + "unist-util-position": "3.1.0", + "unist-util-visit": "2.0.3" + } + }, + "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": { - "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", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.0", - "xtend": "^4.0.1" + "@types/unist": "2.0.3", + "longest-streak": "2.0.4", + "mdast-util-to-string": "2.0.0", + "parse-entities": "2.0.0", + "repeat-string": "1.6.1", + "zwitch": "1.0.5" + }, + "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": { @@ -16028,15 +14460,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": { - "github-slugger": "^1.2.1", - "mdast-util-to-string": "^1.0.5", - "unist-util-is": "^2.1.2", - "unist-util-visit": "^1.1.0" + "@types/mdast": "3.0.3", + "@types/unist": "2.0.3", + "extend": "3.0.2", + "github-slugger": "1.3.0", + "mdast-util-to-string": "2.0.0", + "unist-util-is": "4.1.0", + "unist-util-visit": "2.0.3" }, "dependencies": { "emoji-regex": { @@ -16051,13 +14486,13 @@ "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", "dev": true, "requires": { - "emoji-regex": ">=6.0.0 <=6.1.1" + "emoji-regex": "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 } } @@ -16079,7 +14514,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" }, "dependencies": { "mimic-fn": { @@ -16091,19 +14526,27 @@ } }, "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "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.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" + }, + "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": { @@ -16112,8 +14555,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "errno": "0.1.8", + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -16122,56 +14565,199 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + } + } + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "1.2.1", + "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.2", + "read-pkg-up": "7.0.1", + "redent": "3.0.0", + "trim-newlines": "3.0.1", + "type-fest": "0.18.1", + "yargs-parser": "20.2.9" + }, + "dependencies": { + "camelcase": { + "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.2.1", + "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.2", + "resolve": "1.20.0", + "semver": "7.3.5", + "validate-npm-package-license": "3.0.4" } - } - } - }, - "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=", - "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": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + }, + "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.14.5", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "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": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.2.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.8.9", + "resolve": "1.20.0", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" + } + }, + "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": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "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 } } }, @@ -16186,7 +14772,7 @@ "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "source-map": "^0.6.1" + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -16208,14 +14794,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.3.1", + "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.4", + "micromark-extension-gfm-autolink-literal": "0.5.7", + "micromark-extension-gfm-strikethrough": "0.6.5", + "micromark-extension-gfm-table": "0.4.3", + "micromark-extension-gfm-tagfilter": "0.3.0", + "micromark-extension-gfm-task-list-item": "0.3.3" + } + }, + "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.4" + } + }, + "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.4" + } + }, + "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.4" + } + }, + "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.4" + } + }, "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" + "braces": "3.0.2", + "picomatch": "2.3.0" } }, "miller-rabin": { @@ -16224,14 +14893,14 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "bn.js": "4.12.0", + "brorand": "1.1.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==", + "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 } } @@ -16242,16 +14911,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": { @@ -16266,6 +14935,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", @@ -16284,7 +14959,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -16293,14 +14968,33 @@ "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", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -16309,19 +15003,16 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } }, "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" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "mkdirp-classic": { "version": "0.5.3", @@ -16363,18 +15054,24 @@ "ms": "2.0.0" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "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" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "he": { @@ -16404,31 +15101,39 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } }, + "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-sortable": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-4.0.6.tgz", - "integrity": "sha1-ElGkuixEqS32mJvQKdoSGk8hCbA=", - "dev": true, - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^1.7.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^4.0.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" + "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.3.5", + "browser-resolve": "1.11.3", + "cached-path-relative": "1.0.2", + "concat-stream": "1.5.2", + "defined": "1.0.0", + "detective": "5.2.0", + "duplexer2": "0.1.4", + "inherits": "2.0.3", + "konan": "2.1.1", + "readable-stream": "2.3.7", + "resolve": "1.20.0", + "standard-version": "9.3.0", + "stream-combiner2": "1.1.1", + "subarg": "1.0.0", + "through2": "2.0.5", + "xtend": "4.0.2" }, "dependencies": { "concat-stream": { @@ -16437,9 +15142,9 @@ "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" }, "dependencies": { "readable-stream": { @@ -16448,12 +15153,12 @@ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" } } } @@ -16470,13 +15175,13 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" }, "dependencies": { "process-nextick-args": { @@ -16491,7 +15196,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -16501,6 +15206,16 @@ "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.7", + "xtend": "4.0.2" + } } } }, @@ -16510,11 +15225,11 @@ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", "dev": true, "requires": { - "basic-auth": "~2.0.1", + "basic-auth": "2.0.1", "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "depd": "2.0.0", + "on-finished": "2.3.0", + "on-headers": "1.0.2" }, "dependencies": { "depd": { @@ -16545,7 +15260,7 @@ "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "dev": true, "requires": { - "readable-stream": "~1.1.9" + "readable-stream": "1.1.14" } }, "isarray": { @@ -16560,10 +15275,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -16587,29 +15302,35 @@ "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", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "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" + "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.3", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "natural-compare": { @@ -16630,9 +15351,9 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "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 }, "next-tick": { @@ -16653,11 +15374,11 @@ "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", "dev": true, "requires": { - "@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" + "@sinonjs/formatio": "3.2.2", + "@sinonjs/text-encoding": "0.7.1", + "just-extend": "4.2.1", + "lolex": "5.1.2", + "path-to-regexp": "1.8.0" }, "dependencies": { "@sinonjs/formatio": { @@ -16666,8 +15387,8 @@ "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/commons": "1.8.3", + "@sinonjs/samsam": "3.3.3" } }, "isarray": { @@ -16682,7 +15403,7 @@ "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "1.8.3" } }, "path-to-regexp": { @@ -16696,20 +15417,10 @@ } } }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "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": { @@ -16718,29 +15429,29 @@ "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", "dev": true, "requires": { - "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", + "assert": "1.5.0", + "browserify-zlib": "0.2.0", + "buffer": "4.9.2", + "console-browserify": "1.2.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "domain-browser": "1.2.0", + "events": "3.3.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", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.7", + "stream-browserify": "2.0.2", + "stream-http": "2.8.3", + "string_decoder": "1.3.0", + "timers-browserify": "2.0.12", "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" + "url": "0.11.0", + "util": "0.11.1", + "vm-browserify": "1.1.2" }, "dependencies": { "buffer": { @@ -16749,9 +15460,9 @@ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "1.5.1", + "ieee754": "1.2.1", + "isarray": "1.0.0" } }, "punycode": { @@ -16766,48 +15477,32 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + }, + "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.2" + } + } } } } }, - "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=", - "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==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - } - } - }, "node-releases": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, "nopt": { @@ -16816,7 +15511,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } }, "normalize-package-data": { @@ -16825,126 +15520,56 @@ "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" - } - }, - "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 - }, - "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 - }, - "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, - "requires": { - "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==", - "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" + "hosted-git-info": "2.8.9", + "resolve": "1.20.0", + "semver": "5.7.1", + "validate-npm-package-license": "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==", - "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=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^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=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, + "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 + }, + "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 + }, + "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, + "requires": { + "once": "1.4.0" + } + }, "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, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" + }, + "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 + } } }, "null-check": { @@ -16959,12 +15584,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", @@ -16977,21 +15596,15 @@ "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", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -17000,44 +15613,41 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, - "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" + "is-buffer": "1.1.6" } } } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + "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": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -17045,18 +15655,19 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" } }, "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.2", + "define-properties": "1.1.3", + "has-symbols": "1.0.2", + "object-keys": "1.1.1" } }, "object.defaults": { @@ -17065,20 +15676,10 @@ "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "dev": true, "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "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" + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" } }, "object.map": { @@ -17087,8 +15688,8 @@ "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "object.omit": { @@ -17097,8 +15698,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" }, "dependencies": { "for-own": { @@ -17107,7 +15708,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } } } @@ -17118,7 +15719,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "object.reduce": { @@ -17127,20 +15728,19 @@ "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "call-bind": "1.0.2", + "define-properties": "1.1.3", + "es-abstract": "1.18.3" } }, "on-finished": { @@ -17163,22 +15763,22 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "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" + "mimic-fn": "2.1.0" } }, "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": { @@ -17187,7 +15787,7 @@ "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "is-wsl": "1.1.0" }, "dependencies": { "is-wsl": { @@ -17204,8 +15804,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -17223,17 +15823,85 @@ } }, "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" + "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.1", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.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.3.0", + "supports-color": "7.2.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" + } + } } }, "ordered-read-streams": { @@ -17242,7 +15910,7 @@ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -17251,13 +15919,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -17268,55 +15945,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": { @@ -17326,280 +15961,81 @@ "dev": true }, "p-cancelable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "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-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "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": "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-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": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "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 - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", - "dev": true, - "requires": { - "asn1.js": "^4.0.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 - }, - "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" - } - } + }, + "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, + "requires": { + "p-try": "2.2.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.3.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 + }, + "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.1.0" + } + }, + "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, + "requires": { + "asn1.js": "5.4.1", + "browserify-aes": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.1.2", + "safe-buffer": "5.1.2" } }, "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", - "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" + "character-entities": "1.2.4", + "character-entities-legacy": "1.1.4", + "character-reference-invalid": "1.1.4", + "is-alphanumerical": "1.0.4", + "is-decimal": "1.0.4", + "is-hexadecimal": "1.0.4" } }, "parse-filepath": { @@ -17608,19 +16044,16 @@ "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "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", @@ -17628,10 +16061,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" }, "dependencies": { "is-extglob": { @@ -17646,7 +16079,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } } } @@ -17657,7 +16090,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.2" } }, "parse-ms": { @@ -17679,63 +16112,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" + "is-ssh": "1.3.3", + "protocols": "1.4.8", + "qs": "6.10.1", + "query-string": "6.14.1" }, "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.3", + "normalize-url": "4.5.0", + "parse-path": "4.0.3", + "protocols": "1.4.8" + }, + "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": { @@ -17762,9 +16178,9 @@ "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=", + "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 }, "path-is-absolute": { @@ -17780,21 +16196,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==", - "dev": true - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "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-root": { @@ -17803,7 +16213,7 @@ "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "dev": true, "requires": { - "path-root-regex": "^0.1.0" + "path-root-regex": "0.1.2" } }, "path-root-regex": { @@ -17823,15 +16233,15 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "graceful-fs": "4.2.6", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "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": { @@ -17840,20 +16250,20 @@ "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { - "through": "~2.3" + "through": "2.3.8" } }, "pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-wHMFZ6HTLGlB9f/WsQBs5OwMQJoLXYuJUzbA+j+hRBf7+Y8KcXpatzIviIcTy1OAyhWQp08nyiPO8Dnv0z4Sww==", + "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", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" } }, "pbkdf2-compat": { @@ -17875,15 +16285,9 @@ "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 - }, - "pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "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": { @@ -17904,91 +16308,88 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "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, "requires": { - "node-modules-regexp": "^1.0.0" + "find-up": "4.1.0" } }, - "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==", + "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": "^3.0.0" + "find-up": "2.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==", + "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": "^3.0.0" + "locate-path": "2.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==", + "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": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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": "^2.0.0" + "p-try": "1.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==", + "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": "^2.0.0" + "p-limit": "1.3.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==", + "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-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" - } - }, "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, "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" }, "dependencies": { "ansi-colors": { @@ -17997,7 +16398,7 @@ "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { - "ansi-wrap": "^0.1.0" + "ansi-wrap": "0.1.0" } } } @@ -18008,28 +16409,101 @@ "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 - }, "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 }, - "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 + "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" + } }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "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.3" + } + }, + "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 + }, + "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.1.0", + "postcss-selector-parser": "6.0.6", + "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.6" + } + }, + "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.1.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, + "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.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "preserve": { @@ -18039,40 +16513,21 @@ "dev": true }, "pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "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": "^25.5.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" + "@jest/types": "27.0.2", + "ansi-regex": "5.0.0", + "ansi-styles": "5.2.0", + "react-is": "17.0.2" }, "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==", + "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 } } @@ -18084,18 +16539,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" + "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": { @@ -18116,34 +16571,27 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "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.2" } }, - "property-information": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-3.2.0.tgz", - "integrity": "sha1-/RSDyPusYYCPX+NZ52k6H0ilgzE=", - "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" } }, @@ -18165,7 +16613,7 @@ "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", "dev": true, "requires": { - "event-stream": "=3.3.4" + "event-stream": "3.3.4" } }, "pseudomap": { @@ -18186,18 +16634,18 @@ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { - "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" + "bn.js": "4.12.0", + "browserify-rsa": "4.1.0", + "create-hash": "1.2.0", + "parse-asn1": "5.1.6", + "randombytes": "2.1.0", + "safe-buffer": "5.1.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==", + "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 } } @@ -18208,8 +16656,8 @@ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.4", + "once": "1.4.0" } }, "pumpify": { @@ -18218,9 +16666,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.7.1", + "inherits": "2.0.3", + "pump": "2.0.1" }, "dependencies": { "pump": { @@ -18229,8 +16677,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.4", + "once": "1.4.0" } } } @@ -18242,38 +16690,59 @@ "dev": true }, "puppeteer-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-3.3.0.tgz", - "integrity": "sha512-hynQ3r0J/lkGrKeBCqu160jrj0WhthYLIzDQPkBxLzxPokjw4elk1sn6mXAian/kfD2NRzpdh9FSykxZyL56uA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "mime": "^2.0.3", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "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.3.1", + "devtools-protocol": "0.0.869402", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.1", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "7.5.0" }, "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.3.1" + } + }, "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.0.2", + "debug": "4.3.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -18286,7 +16755,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.7" } } } @@ -18308,14 +16777,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.1.0", + "strict-uri-encode": "2.0.0" } }, "querystring": { @@ -18331,9 +16808,9 @@ "dev": true }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, "quick-lru": { @@ -18348,9 +16825,9 @@ "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", "dev": true, "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" + "is-number": "4.0.0", + "kind-of": "6.0.3", + "math-random": "1.0.4" }, "dependencies": { "is-number": { @@ -18367,7 +16844,7 @@ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { - "safe-buffer": "^5.1.0" + "safe-buffer": "5.1.2" } }, "randomfill": { @@ -18376,8 +16853,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "randombytes": "2.1.0", + "safe-buffer": "5.1.2" } }, "range-parser": { @@ -18397,9 +16874,9 @@ } }, "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-pkg": { @@ -18408,9 +16885,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "load-json-file": "1.1.0", + "normalize-package-data": "2.5.0", + "path-type": "1.1.0" } }, "read-pkg-up": { @@ -18419,8 +16896,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "1.1.2", + "read-pkg": "1.1.0" }, "dependencies": { "find-up": { @@ -18429,8 +16906,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "path-exists": { @@ -18439,7 +16916,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "pinkie-promise": "2.0.1" } } } @@ -18450,27 +16927,27 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "inherits": "2.0.3", + "string_decoder": "1.3.0", + "util-deprecate": "1.0.2" } }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "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": { - "picomatch": "^2.2.1" + "minimatch": "3.0.4" } }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "util.promisify": "^1.0.0" + "picomatch": "2.3.0" } }, "rechoir": { @@ -18479,34 +16956,40 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.20.0" + } + }, + "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, + "requires": { + "minimatch": "3.0.4" } }, "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.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "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": { @@ -18515,7 +16998,7 @@ "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", "dev": true, "requires": { - "regenerate": "^1.4.0" + "regenerate": "1.4.2" } }, "regenerator-runtime": { @@ -18524,13 +17007,12 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "7.14.6" } }, "regex-cache": { @@ -18539,7 +17021,7 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "regex-not": { @@ -18548,37 +17030,38 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "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", - "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" + "regenerate": "1.4.2", + "regenerate-unicode-properties": "8.2.0", + "regjsgen": "0.5.2", + "regjsparser": "0.6.9", + "unicode-match-property-ecmascript": "1.0.4", + "unicode-match-property-value-ecmascript": "1.2.0" } }, "regjsgen": { @@ -18588,12 +17071,12 @@ "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" + "jsesc": "0.5.0" }, "dependencies": { "jsesc": { @@ -18605,110 +17088,72 @@ } }, "remark": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", - "integrity": "sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==", + "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": { + "remark-parse": "9.0.0", + "remark-stringify": "9.0.1", + "unified": "9.2.1" + } + }, + "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": { - "remark-parse": "^5.0.0", - "remark-stringify": "^5.0.0", - "unified": "^6.0.0" + "mdast-util-gfm": "0.1.2", + "micromark-extension-gfm": "0.3.3" } }, "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.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": { - "hast-util-sanitize": "^1.0.0", - "hast-util-to-html": "^3.0.0", - "mdast-util-to-hast": "^3.0.0", - "xtend": "^4.0.1" + "hast-util-sanitize": "3.0.2", + "hast-util-to-html": "7.1.3", + "mdast-util-to-hast": "10.2.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==", - "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" - } - }, - "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", "dev": true, "requires": { - "unist-util-visit": "^1.0.0" + "mdast-util-from-markdown": "0.8.5" } }, - "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-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, "requires": { - "github-slugger": "^1.0.0", - "mdast-util-to-string": "^1.0.0", - "unist-util-visit": "^1.0.0" + "unist-util-visit": "2.0.3" } }, "remark-stringify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-5.0.0.tgz", - "integrity": "sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==", - "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" - } - }, - "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==", + "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.5" } }, - "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.1.0" } }, "remove-bom-buffer": { @@ -18717,16 +17162,8 @@ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", "dev": true, "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 - } + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" } }, "remove-bom-stream": { @@ -18735,9 +17172,45 @@ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.2", + "through2": "2.0.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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" + } + } } }, "remove-trailing-separator": { @@ -18747,9 +17220,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": { @@ -18764,13 +17237,13 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.1.0" } }, "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": { @@ -18779,9 +17252,9 @@ "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" + "homedir-polyfill": "1.0.3", + "is-absolute": "1.0.0", + "remove-trailing-separator": "1.1.0" } }, "replacestream": { @@ -18790,9 +17263,9 @@ "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1", + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -18801,13 +17274,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -18818,26 +17300,26 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { - "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" + "aws-sign2": "0.7.0", + "aws4": "1.11.0", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.5", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.31", + "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.4.0" }, "dependencies": { "qs": { @@ -18854,32 +17336,18 @@ } } }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, "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 }, + "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", @@ -18892,8 +17360,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "caller-path": "0.1.0", + "resolve-from": "1.0.1" }, "dependencies": { "resolve-from": { @@ -18911,45 +17379,29 @@ "dev": true }, "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": { - "path-parse": "^1.0.6" + "is-core-module": "2.4.0", + "path-parse": ">=1.0.7" } }, "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", "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" } }, "resolve-from": { @@ -18964,7 +17416,7 @@ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "value-or-function": "^3.0.0" + "value-or-function": "3.0.0" } }, "resolve-url": { @@ -18979,16 +17431,16 @@ "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "dev": true, "requires": { - "lowercase-keys": "^2.0.0" + "lowercase-keys": "2.0.0" } }, "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" + "fast-deep-equal": "2.0.1" }, "dependencies": { "fast-deep-equal": { @@ -19005,8 +17457,8 @@ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "5.1.2", + "signal-exit": "3.0.3" } }, "ret": { @@ -19016,15 +17468,15 @@ "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 }, "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": { @@ -19033,277 +17485,118 @@ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, "requires": { - "align-text": "^0.1.1" - } - }, - "rimraf": { - "version": "2.5.4", - "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": "*" + "align-text": "0.1.4" } - }, - "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "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=", - "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" - } - } - } - }, - "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" - } - }, - "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" - } - }, - "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" - } - } + }, + "rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, + "requires": { + "glob": "7.1.7" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "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.1.0", + "inherits": "2.0.3" + } + }, + "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": "4.0.8" + } + }, + "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.14.1" + } + }, + "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.15" + } + }, + "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.7", + "ajv": "6.12.6", + "ajv-keywords": "3.5.2" }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "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" + "fast-deep-equal": "3.1.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.4.1" } } } }, "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": { @@ -19312,7 +17605,7 @@ "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", "dev": true, "requires": { - "sver-compat": "^1.5.0" + "sver-compat": "1.5.0" } }, "send": { @@ -19321,18 +17614,18 @@ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "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", + "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", + "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" + "on-finished": "2.3.0", + "range-parser": "1.2.1", + "statuses": "1.5.0" }, "dependencies": { "ms": { @@ -19343,35 +17636,44 @@ } }, "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": "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", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "1.3.7", "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" + "escape-html": "1.0.3", + "http-errors": "1.6.3", + "mime-types": "2.1.31", + "parseurl": "1.3.3" }, "dependencies": { "http-errors": { @@ -19380,10 +17682,10 @@ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "statuses": "1.5.0" } }, "setprototypeof": { @@ -19399,9 +17701,9 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.3", "send": "0.17.1" } }, @@ -19417,10 +17719,10 @@ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -19429,7 +17731,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -19451,29 +17753,23 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "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=", - "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": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "shelljs": { @@ -19482,16 +17778,21 @@ "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "dev": true, "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" + "glob": "7.1.7", + "interpret": "1.4.0", + "rechoir": "0.6.2" } }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true + "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, + "requires": { + "call-bind": "1.0.2", + "get-intrinsic": "1.1.1", + "object-inspect": "1.10.3" + } }, "signal-exit": { "version": "3.0.3", @@ -19505,21 +17806,23 @@ "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", "dev": true, "requires": { - "@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" + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.7.5", + "nise": "1.5.3", + "supports-color": "5.5.0", + "type-detect": "4.0.8" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + } } }, - "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", @@ -19532,19 +17835,18 @@ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "4.3.0", + "astral-regex": "2.0.0", + "is-fullwidth-code-point": "3.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==", + "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" + "color-convert": "2.0.1" } }, "color-convert": { @@ -19553,7 +17855,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -19570,14 +17872,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "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" + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.3", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -19586,7 +17888,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -19595,7 +17897,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -19606,9 +17908,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -19617,7 +17919,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -19626,7 +17928,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -19635,7 +17937,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -19644,9 +17946,9 @@ "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" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } } } @@ -19657,139 +17959,88 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.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", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } }, "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==", - "dev": 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" + "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.10", + "@types/node": "15.12.4", + "accepts": "1.3.7", + "base64id": "2.0.0", + "debug": "4.3.1", + "engine.io": "4.1.1", + "socket.io-adapter": "2.1.0", + "socket.io-parser": "4.0.4" }, "dependencies": { "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": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "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-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" - }, - "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==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "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", @@ -19802,34 +18053,57 @@ "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", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "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" + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.1", + "urix": "0.1.0" } }, "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.1.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 + } } }, "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", @@ -19848,8 +18122,8 @@ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.1", + "spdx-license-ids": "3.0.9" } }, "spdx-exceptions": { @@ -19864,14 +18138,14 @@ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.3.0", + "spdx-license-ids": "3.0.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": { @@ -19880,16 +18154,31 @@ "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { - "through": "2" + "through": "2.3.8" } }, + "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", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" + } + }, + "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.6.0" } }, "sprintf-js": { @@ -19904,15 +18193,15 @@ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { - "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" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "stack-trace": { @@ -19922,16 +18211,122 @@ "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 + "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" + }, + "dependencies": { + "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 + } + } }, - "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.1.0", + "detect-newline": "3.1.0", + "dotgitignore": "2.1.0", + "figures": "3.2.0", + "find-up": "5.0.0", + "fs-access": "1.0.1", + "git-semver-tags": "4.1.1", + "semver": "7.3.5", + "stringify-package": "1.0.1", + "yargs": "16.2.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.1.0" + } + }, + "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.4", + "escalade": "3.1.1", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "string-width": "4.2.2", + "y18n": "5.0.8", + "yargs-parser": "20.2.9" + } + } + } }, "static-extend": { "version": "0.1.2", @@ -19939,8 +18334,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -19949,7 +18344,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -19959,19 +18354,13 @@ "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", "integrity": "sha1-nl9zRfITfDDuO0mLkRToC1K7frU=", "dev": true, "requires": { - "readable-stream": "~2.1.0" + "readable-stream": "2.1.5" }, "dependencies": { "process-nextick-args": { @@ -19986,13 +18375,13 @@ "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", "dev": true, "requires": { - "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" + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -20009,8 +18398,8 @@ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" + "inherits": "2.0.3", + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -20019,13 +18408,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -20042,7 +18440,7 @@ "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "duplexer": "0.1.2" } }, "stream-combiner2": { @@ -20051,8 +18449,8 @@ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "duplexer2": "0.1.4", + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { @@ -20061,13 +18459,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -20084,11 +18491,11 @@ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", "dev": true, "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.7", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.2" }, "dependencies": { "readable-stream": { @@ -20097,13 +18504,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -20115,45 +18531,40 @@ "dev": true }, "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", "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" + "date-format": "2.1.0", + "debug": "4.3.1", + "fs-extra": "8.1.0" }, "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } + "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", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.6", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "jsonfile": { @@ -20162,7 +18573,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.6" } }, "ms": { @@ -20180,43 +18591,17 @@ } }, "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=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "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=", + "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": { - "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 - }, - "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 - }, - "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" - } - } - } + "optional": true }, "string-template": { "version": "0.2.1", @@ -20225,92 +18610,77 @@ "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==", + "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.0-next.1" + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" } }, "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "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==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "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==", + "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.17.5", - "string.prototype.trimend": "^1.0.0" + "call-bind": "1.0.2", + "define-properties": "1.1.3" } }, "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "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==", + "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": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "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 + } } }, "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" + "character-entities-html4": "1.1.4", + "character-entities-legacy": "1.1.4", + "xtend": "4.0.2" } }, + "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 + }, "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, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "5.0.0" } }, "strip-bom": { @@ -20319,7 +18689,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "strip-bom-string": { @@ -20335,18 +18705,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.1" } }, "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=", + "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": { @@ -20355,7 +18725,7 @@ "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", "dev": true, "requires": { - "minimist": "^1.1.0" + "minimist": "1.2.5" } }, "suffix": { @@ -20370,7 +18740,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "sver-compat": { @@ -20379,277 +18749,153 @@ "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", "dev": true, "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.3" } }, - "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 - }, "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.6.0", + "lodash.clonedeep": "4.5.0", + "lodash.truncate": "4.4.2", + "slice-ansi": "4.0.0", + "string-width": "4.2.2", + "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 - }, - "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": { - "is-fullwidth-code-point": "^2.0.0" - } - }, - "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.3", + "json-schema-traverse": "1.0.0", + "require-from-string": "2.0.2", + "uri-js": "4.4.1" } }, - "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 } } }, "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", - "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", - "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==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - }, - "tar-stream": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", - "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", - "dev": true, - "requires": { - "bl": "^4.0.1", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "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, - "requires": { - "rimraf": "~2.5.2" - } - }, - "ternary-stream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz", - "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", - "dev": true, - "requires": { - "duplexify": "^3.5.0", - "fork-stream": "^0.0.4", - "merge-stream": "^1.0.0", - "through2": "^2.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=", - "dev": true, - "requires": { - "readable-stream": "^2.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" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.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==", - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.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" - } - }, - "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 - }, - "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=", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", + "dev": true + }, + "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, + "requires": { + "chownr": "1.1.4", + "mkdirp-classic": "0.5.3", + "pump": "3.0.0", + "tar-stream": "2.2.0" + } + }, + "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, + "requires": { + "bl": "4.1.0", + "end-of-stream": "1.4.4", + "fs-constants": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "3.6.0" + } + }, + "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, + "requires": { + "rimraf": "2.5.4" + } + }, + "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, + "requires": { + "duplexify": "4.1.1", + "fork-stream": "0.0.4", + "merge-stream": "2.0.0", + "through2": "3.0.2" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "end-of-stream": "1.4.4", + "inherits": "2.0.3", + "readable-stream": "3.6.0", + "stream-shift": "1.0.1" } }, - "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==", + "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": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "inherits": "2.0.4", + "readable-stream": "3.6.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 + } } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + } + } + }, + "terser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.4.0.tgz", + "integrity": "sha512-3dZunFLbCJis9TAF2VnX+VrQLctRUmt1p3W2kCsJuZE4ZgWqh//+1MZ62EanewrqKoUf4zIaDGZAvml4UDc0OQ==", + "dev": true, + "requires": { + "commander": "2.20.3", + "source-map": "0.7.3", + "source-map-support": "0.5.19" + }, + "dependencies": { + "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", @@ -20657,15 +18903,9 @@ "dev": true }, "textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==", - "dev": true - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", "dev": true }, "through": { @@ -20675,13 +18915,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": "3.6.0" + } + }, + "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": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "through2": "2.0.5", + "xtend": "4.0.2" }, "dependencies": { "readable-stream": { @@ -20690,46 +18939,49 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" } } } }, - "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", "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", "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=", - "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" + "setimmediate": "1.0.5" } }, "timers-ext": { @@ -20738,8 +18990,8 @@ "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "0.10.53", + "next-tick": "1.0.0" } }, "tiny-hashes": { @@ -20753,27 +19005,27 @@ "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", "dev": true, "requires": { - "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" + "body": "5.1.0", + "debug": "3.2.7", + "faye-websocket": "0.10.0", + "livereload-js": "2.4.0", + "object-assign": "4.1.1", + "qs": "6.7.0" }, "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": "2.1.3" } }, "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 } } @@ -20784,31 +19036,19 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "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", "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "is-absolute": "1.0.0", + "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", @@ -20827,22 +19067,16 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.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", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -20853,10 +19087,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -20865,7 +19099,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-number": "7.0.0" } }, "to-through": { @@ -20874,7 +19108,43 @@ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "through2": "^2.0.3" + "through2": "2.0.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.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" + } + } } }, "toidentifier": { @@ -20888,17 +19158,8 @@ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "psl": "^1.1.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" + "psl": "1.8.0", + "punycode": "2.1.1" } }, "traverse": { @@ -20907,34 +19168,16 @@ "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==", - "dev": true - }, "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "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-right": { + "trim-off-newlines": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "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==", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, "trough": { @@ -20949,10 +19192,39 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, + "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, + "requires": { + "@types/json5": "0.0.29", + "json5": "1.0.1", + "minimist": "1.2.5", + "strip-bom": "3.0.0" + }, + "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.5" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "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": { @@ -20967,7 +19239,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -20983,12 +19255,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": { @@ -20998,9 +19270,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": { @@ -21009,7 +19281,7 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "mime-types": "2.1.31" } }, "typedarray": { @@ -21023,7 +19295,7 @@ "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", "requires": { - "typescript-logic": "^0.0.0" + "typescript-logic": "0.0.0" } }, "typescript-logic": { @@ -21036,23 +19308,21 @@ "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", "requires": { - "typescript-compare": "^0.0.2" + "typescript-compare": "0.0.2" } }, "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.9.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.4.tgz", - "integrity": "sha512-8RZBJq5smLOa7KslsNsVcSH+KOXf1uDU8yqLeNuVKwmT0T3FA0ZoXlinQfRad7SDcbZZRZE4ov+2v71EnxNyCA==", + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz", + "integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==", "dev": true, - "requires": { - "commander": "~2.20.3" - } + "optional": true }, "uglify-to-browserify": { "version": "1.0.2", @@ -21066,9 +19336,9 @@ "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", "dev": true, "requires": { - "source-map": "^0.5.6", - "uglify-js": "^2.8.29", - "webpack-sources": "^1.0.1" + "source-map": "0.5.7", + "uglify-js": "2.8.29", + "webpack-sources": "1.4.3" }, "dependencies": { "camelcase": { @@ -21083,20 +19353,26 @@ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", + "center-align": "0.1.3", + "right-align": "0.1.3", "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", "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dev": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" } }, "wordwrap": { @@ -21111,19 +19387,25 @@ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", "window-size": "0.1.0" } } } }, - "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 + "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", @@ -21131,8 +19413,8 @@ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" + "buffer": "5.7.1", + "through": "2.3.8" } }, "unc-path-regex": { @@ -21142,20 +19424,29 @@ "dev": true }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", - "dev": true, - "requires": { - "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", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "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.1.0", + "arr-map": "2.0.2", + "bach": "1.2.0", + "collection-map": "1.0.0", + "es6-weak-map": "2.0.3", + "fast-levenshtein": "1.1.4", + "last-run": "1.1.1", + "object.defaults": "1.1.0", + "object.reduce": "1.0.1", + "undertaker-registry": "1.0.1" + }, + "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": { @@ -21164,16 +19455,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", @@ -21186,8 +19467,8 @@ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", "dev": true, "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "unicode-canonical-property-names-ecmascript": "1.0.4", + "unicode-property-aliases-ecmascript": "1.1.0" } }, "unicode-match-property-value-ecmascript": { @@ -21203,17 +19484,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", - "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" + "bail": "1.0.5", + "extend": "3.0.2", + "is-buffer": "2.0.5", + "is-plain-obj": "2.1.0", + "trough": "1.0.5", + "vfile": "4.2.1" + }, + "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": { @@ -21222,10 +19511,10 @@ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "2.0.1" } }, "unique-stream": { @@ -21234,29 +19523,26 @@ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" + "json-stable-stringify-without-jsonify": "1.0.1", + "through2-filter": "3.0.0" } }, "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": { @@ -21265,43 +19551,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.3" } }, - "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.3", + "unist-util-is": "4.1.0", + "unist-util-visit-parents": "3.1.1" } }, "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.3", + "unist-util-is": "4.1.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": { @@ -21315,8 +19598,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -21325,9 +19608,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -21355,15 +19638,15 @@ "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", "dev": true, "requires": { - "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" + "big-integer": "1.6.48", + "binary": "0.3.0", + "bluebird": "3.4.7", + "buffer-indexof-polyfill": "1.0.2", + "duplexer2": "0.1.4", + "fstream": "1.0.12", + "listenercount": "1.0.1", + "readable-stream": "2.3.7", + "setimmediate": "1.0.5" }, "dependencies": { "bluebird": { @@ -21378,13 +19661,22 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" } } } @@ -21396,12 +19688,12 @@ "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" + "punycode": "2.1.1" } }, "urix": { @@ -21431,58 +19723,25 @@ "url-join": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", - "dev": true - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "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=", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", "dev": true }, + "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, + "requires": { + "querystringify": "2.2.0", + "requires-port": "1.0.0" + } + }, "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" - } - }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -21498,27 +19757,21 @@ "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" - } - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", - "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==", + "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": { @@ -21527,7 +19780,7 @@ "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "1.0.3" } }, "validate-npm-package-license": { @@ -21536,8 +19789,8 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.1.1", + "spdx-expression-parse": "3.0.1" } }, "value-or-function": { @@ -21557,107 +19810,62 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "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.3", + "is-buffer": "2.0.5", + "unist-util-stringify-position": "2.0.3", + "vfile-message": "2.0.4" }, "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.3", + "unist-util-stringify-position": "2.0.3" } }, "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", - "vfile-statistics": "^1.1.0" + "repeat-string": "1.6.1", + "string-width": "4.2.2", + "supports-color": "6.1.0", + "unist-util-stringify-position": "2.0.3", + "vfile-sort": "2.2.2", + "vfile-statistics": "1.1.4" }, "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" } } } @@ -21675,17 +19883,17 @@ "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", - "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" + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.3", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.1" }, "dependencies": { "clone": { @@ -21702,23 +19910,23 @@ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, "requires": { - "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" + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.2.6", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.2", + "pumpify": "1.5.1", + "readable-stream": "2.3.7", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.5", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.2.1", + "vinyl-sourcemap": "1.1.0" }, "dependencies": { "readable-stream": { @@ -21727,13 +19935,32 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.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.2" + } + }, + "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.7", + "xtend": "4.0.2" } } } @@ -21744,13 +19971,13 @@ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, "requires": { - "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" + "append-buffer": "1.0.2", + "convert-source-map": "1.8.0", + "graceful-fs": "4.2.6", + "normalize-path": "2.1.1", + "now-and-later": "2.0.1", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.2.1" }, "dependencies": { "normalize-path": { @@ -21759,7 +19986,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } } } @@ -21770,7 +19997,7 @@ "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", "dev": true, "requires": { - "source-map": "^0.5.1" + "source-map": "0.5.7" } }, "vm-browserify": { @@ -21785,13 +20012,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.2.0" } }, "walk": { @@ -21800,38 +20029,29 @@ "integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==", "dev": true, "requires": { - "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" + "foreachasync": "3.0.0" } }, "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", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.0" + "chokidar": "3.5.2", + "graceful-fs": "4.2.6", + "neo-async": "2.6.2", + "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": { - "chokidar": "^2.1.8" + "chokidar": "2.1.8" }, "dependencies": { "anymatch": { @@ -21841,8 +20061,8 @@ "dev": true, "optional": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" }, "dependencies": { "normalize-path": { @@ -21852,7 +20072,7 @@ "dev": true, "optional": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } } } @@ -21869,18 +20089,17 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, - "optional": 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" + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -21888,9 +20107,8 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -21902,18 +20120,18 @@ "dev": true, "optional": 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" + "anymatch": "2.0.0", + "async-each": "1.0.3", + "braces": "2.3.2", + "fsevents": "1.2.13", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.2.0" } }, "fill-range": { @@ -21921,12 +20139,11 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, - "optional": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -21934,9 +20151,8 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -21948,8 +20164,8 @@ "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "bindings": "1.5.0", + "nan": "2.14.2" } }, "glob-parent": { @@ -21959,8 +20175,8 @@ "dev": true, "optional": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -21970,7 +20186,7 @@ "dev": true, "optional": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -21982,24 +20198,16 @@ "dev": true, "optional": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.13.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, - "optional": 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, - "optional": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -22007,9 +20215,8 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -22019,21 +20226,20 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, - "optional": 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" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "readable-stream": { @@ -22043,13 +20249,13 @@ "dev": true, "optional": 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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "readdirp": { @@ -22059,9 +20265,19 @@ "dev": true, "optional": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "graceful-fs": "4.2.6", + "micromatch": "3.1.10", + "readable-stream": "2.3.7" + } + }, + "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.2" } }, "to-regex-range": { @@ -22069,10 +20285,9 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, - "optional": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } } } @@ -22082,54 +20297,200 @@ "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" + "defaults": "1.0.3" } }, "webdriver": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.1.14.tgz", - "integrity": "sha512-6fXoGDnxWfJn9zqfYbc6+VEV5N1obd3K7iuRmJ7w/hH9d9XDxcrcx147T5egiy1qziko61hqYE/x9VtSQbjPcA==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.14", - "@wdio/utils": "6.1.8", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.7.4.tgz", + "integrity": "sha512-bE6/A+OYb040GZ1MiuZebc8bOOYm797dmqEfmj6aoEQ4BMy1juiFlzCzeBzAlPrq33qPa8/CSYfH7rnkB3RRwg==", + "dev": true, + "requires": { + "@types/node": "14.17.4", + "@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.8.2", + "lodash.merge": "4.6.2" + }, + "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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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.1.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.1.16.tgz", - "integrity": "sha512-vk6/XeErNnMooebCtxwIR//LqrastXO9gXL+eVUGNRAuG5omp8XCdT+MK2fmlw53xhcPULQ/y3h8ysYlPnPeyA==", - "dev": true, - "requires": { - "@wdio/config": "6.1.14", - "@wdio/logger": "6.0.16", - "@wdio/repl": "6.1.8", - "@wdio/utils": "6.1.8", - "archiver": "^4.0.1", - "css-value": "^0.0.1", - "devtools": "6.1.16", - "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", - "resq": "^1.6.0", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.1.14" - } - }, - "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 + "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.17.4", + "@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.3.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.4", + "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.1", + "query-selector-shadow-dom": "1.0.0", + "resq": "1.10.0", + "rgb2hex": "0.2.5", + "serialize-error": "8.1.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.1.1", + "loglevel": "1.7.1", + "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.3.0", + "supports-color": "7.2.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" + } + } + } }, "webpack": { "version": "3.12.0", @@ -22137,40 +20498,46 @@ "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", "dev": true, "requires": { - "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" + "acorn": "5.7.4", + "acorn-dynamic-import": "2.0.2", + "ajv": "6.12.6", + "ajv-keywords": "3.5.2", + "async": "2.6.3", + "enhanced-resolve": "3.4.1", + "escope": "3.6.0", + "interpret": "1.4.0", + "json-loader": "0.5.7", + "json5": "0.5.1", + "loader-runner": "2.4.0", + "loader-utils": "1.4.0", + "memory-fs": "0.4.1", + "mkdirp": "0.5.5", + "node-libs-browser": "2.2.1", + "source-map": "0.5.7", + "supports-color": "4.5.0", + "tapable": "0.2.9", + "uglifyjs-webpack-plugin": "0.4.6", + "watchpack": "1.7.5", + "webpack-sources": "1.4.3", + "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.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "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" + "fast-deep-equal": "3.1.3", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.4.1" } }, "ansi-regex": { @@ -22185,7 +20552,7 @@ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "^4.17.14" + "lodash": "4.17.21" } }, "camelcase": { @@ -22194,28 +20561,69 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "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.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.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.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "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.1.5", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "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.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.3", + "strip-eof": "1.0.0" + } + }, + "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": { - "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" - } - } + "locate-path": "2.0.0" } }, "get-caller-file": { @@ -22224,6 +20632,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", @@ -22236,7 +20650,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "json5": { @@ -22251,19 +20665,101 @@ "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" + "graceful-fs": "4.2.6", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.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" + }, + "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.5" + } + } + } + }, + "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" + } + }, + "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" + } + }, + "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", + "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.3.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 + }, "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": "2.3.0" } }, "read-pkg": { @@ -22272,9 +20768,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "load-json-file": "2.0.0", + "normalize-package-data": "2.5.0", + "path-type": "2.0.0" } }, "read-pkg-up": { @@ -22283,8 +20779,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "find-up": "2.1.0", + "read-pkg": "2.0.0" } }, "require-main-filename": { @@ -22293,14 +20789,29 @@ "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", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" }, "dependencies": { "ansi-regex": { @@ -22321,7 +20832,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -22332,7 +20843,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -22347,7 +20858,16 @@ "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { - "has-flag": "^2.0.0" + "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": { @@ -22356,8 +20876,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "string-width": { @@ -22366,17 +20886,17 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "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=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, "yargs": { @@ -22385,19 +20905,19 @@ "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", "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" + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.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.1.1", + "which-module": "2.0.0", + "y18n": "3.2.2", + "yargs-parser": "7.0.0" } }, "yargs-parser": { @@ -22406,57 +20926,54 @@ "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } }, "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==", - "dev": true, - "requires": { - "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.15", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" + "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.4.1", + "acorn-walk": "7.2.0", + "bfj": "6.1.2", + "chalk": "2.4.2", + "commander": "2.20.3", + "ejs": "2.7.4", + "express": "4.17.1", + "filesize": "3.6.1", + "gzip-size": "5.1.1", + "lodash": "4.17.21", + "mkdirp": "0.5.5", + "opener": "1.5.2", + "ws": "6.2.2" }, "dependencies": { - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", - "dev": true - }, - "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", - "dev": true - }, "ejs": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", "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" + } + }, "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" + "async-limiter": "1.0.1" } } } @@ -22467,8 +20984,8 @@ "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", "dev": true, "requires": { - "source-list-map": "~0.1.7", - "source-map": "~0.4.1" + "source-list-map": "0.1.8", + "source-map": "0.4.4" }, "dependencies": { "source-list-map": { @@ -22483,7 +21000,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -22494,19 +21011,19 @@ "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", "dev": true, "requires": { - "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" + "loud-rejection": "1.6.0", + "memory-fs": "0.4.1", + "mime": "2.5.2", + "path-is-absolute": "1.0.1", + "range-parser": "1.2.1", + "url-join": "2.0.5", + "webpack-log": "1.2.0" }, "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 } } @@ -22517,10 +21034,10 @@ "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", "dev": true, "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "loglevelnext": "^1.0.1", - "uuid": "^3.1.0" + "chalk": "2.4.2", + "log-symbols": "2.2.0", + "loglevelnext": "1.0.5", + "uuid": "3.4.0" }, "dependencies": { "log-symbols": { @@ -22529,7 +21046,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" } }, "uuid": { @@ -22546,8 +21063,8 @@ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "source-list-map": "2.0.1", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -22564,13 +21081,13 @@ "integrity": "sha1-Oh0WD7EdQXJ7fObzL3IkZPmLIYY=", "dev": true, "requires": { - "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" + "gulp-util": "3.0.8", + "lodash.clone": "4.5.0", + "lodash.some": "4.6.0", + "memory-fs": "0.3.0", + "through": "2.3.8", + "vinyl": "1.2.0", + "webpack": "1.15.0" }, "dependencies": { "acorn": { @@ -22585,8 +21102,8 @@ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -22595,7 +21112,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "array-unique": { @@ -22628,9 +21145,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.4" } }, "browserify-aes": { @@ -22639,7 +21156,7 @@ "integrity": "sha1-BnFJtmjfMcS1hTPgLQHoBthgjiw=", "dev": true, "requires": { - "inherits": "^2.0.1" + "inherits": "2.0.3" } }, "browserify-zlib": { @@ -22648,7 +21165,7 @@ "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", "dev": true, "requires": { - "pako": "~0.2.0" + "pako": "0.2.9" } }, "buffer": { @@ -22657,9 +21174,9 @@ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "1.5.1", + "ieee754": "1.2.1", + "isarray": "1.0.0" } }, "camelcase": { @@ -22674,15 +21191,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^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" + "anymatch": "1.3.2", + "async-each": "1.0.3", + "fsevents": "1.2.13", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1" } }, "cliui": { @@ -22691,8 +21208,8 @@ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", + "center-align": "0.1.3", + "right-align": "0.1.3", "wordwrap": "0.0.2" } }, @@ -22714,6 +21231,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", @@ -22726,9 +21249,9 @@ "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.2.0", - "tapable": "^0.1.8" + "graceful-fs": "4.2.6", + "memory-fs": "0.2.0", + "tapable": "0.1.10" }, "dependencies": { "memory-fs": { @@ -22751,7 +21274,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "extglob": { @@ -22760,7 +21283,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "fill-range": { @@ -22769,10 +21292,10 @@ "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" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -22781,7 +21304,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -22793,8 +21316,8 @@ "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "bindings": "1.5.0", + "nan": "2.14.2" } }, "glob-parent": { @@ -22803,7 +21326,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "has-flag": { @@ -22830,7 +21353,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" }, "dependencies": { "kind-of": { @@ -22847,22 +21370,16 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.13.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-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" + "kind-of": "6.0.3" }, "dependencies": { "kind-of": { @@ -22879,9 +21396,9 @@ "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" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" }, "dependencies": { "kind-of": { @@ -22904,7 +21421,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-number": { @@ -22913,7 +21430,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "json5": { @@ -22928,7 +21445,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "loader-utils": { @@ -22937,10 +21454,10 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" } }, "memory-fs": { @@ -22949,8 +21466,8 @@ "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "errno": "0.1.8", + "readable-stream": "2.3.7" } }, "micromatch": { @@ -22959,19 +21476,28 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "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" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "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" } }, "node-libs-browser": { @@ -22980,28 +21506,28 @@ "integrity": "sha1-PicsCBnjCJNeJmdECNevDhSRuDs=", "dev": true, "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.1.4", - "buffer": "^4.9.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", + "assert": "1.5.0", + "browserify-zlib": "0.1.4", + "buffer": "4.9.2", + "console-browserify": "1.2.0", + "constants-browserify": "1.0.0", "crypto-browserify": "3.3.0", - "domain-browser": "^1.1.1", - "events": "^1.0.0", + "domain-browser": "1.2.0", + "events": "1.1.1", "https-browserify": "0.0.1", - "os-browserify": "^0.2.0", + "os-browserify": "0.2.1", "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", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.7", + "stream-browserify": "2.0.2", + "stream-http": "2.8.3", + "string_decoder": "0.10.31", + "timers-browserify": "2.0.12", "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.10.3", + "url": "0.11.0", + "util": "0.10.4", "vm-browserify": "0.0.4" }, "dependencies": { @@ -23019,7 +21545,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "os-browserify": { @@ -23052,13 +21578,13 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "readdirp": { @@ -23067,9 +21593,9 @@ "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" + "graceful-fs": "4.2.6", + "micromatch": "3.1.10", + "readable-stream": "2.3.7" }, "dependencies": { "arr-diff": { @@ -23090,16 +21616,16 @@ "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" + "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.4", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -23108,7 +21634,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -23119,13 +21645,13 @@ "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" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -23134,7 +21660,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -23143,7 +21669,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-descriptor": { @@ -23152,9 +21678,9 @@ "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" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -23171,14 +21697,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "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" + "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.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -23187,7 +21713,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -23196,7 +21722,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -23207,7 +21733,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -23216,7 +21742,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -23227,7 +21753,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -23236,7 +21762,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -23253,19 +21779,19 @@ "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" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } } } @@ -23288,13 +21814,22 @@ "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.2" + } + }, "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" + "has-flag": "1.0.0" } }, "tapable": { @@ -23309,8 +21844,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "uglify-js": { @@ -23319,10 +21854,10 @@ "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", "dev": true, "requires": { - "async": "~0.2.6", - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "async": "0.2.10", + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" }, "dependencies": { "async": { @@ -23348,8 +21883,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } }, @@ -23368,9 +21903,9 @@ "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", "dev": true, "requires": { - "async": "^0.9.0", - "chokidar": "^1.0.0", - "graceful-fs": "^4.1.2" + "async": "0.9.2", + "chokidar": "1.7.0", + "graceful-fs": "4.2.6" }, "dependencies": { "async": { @@ -23387,21 +21922,21 @@ "integrity": "sha1-T/MfU9sDM55VFkqdRo7gMklo/pg=", "dev": true, "requires": { - "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" + "acorn": "3.3.0", + "async": "1.5.2", + "clone": "1.0.4", + "enhanced-resolve": "0.9.1", + "interpret": "0.6.6", + "loader-utils": "0.2.17", + "memory-fs": "0.3.0", + "mkdirp": "0.5.5", + "node-libs-browser": "0.7.0", + "optimist": "0.6.1", + "supports-color": "3.2.3", + "tapable": "0.1.10", + "uglify-js": "2.7.5", + "watchpack": "0.2.9", + "webpack-core": "0.6.9" } }, "wordwrap": { @@ -23416,9 +21951,9 @@ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", "window-size": "0.1.0" } } @@ -23430,9 +21965,9 @@ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" + "http-parser-js": "0.5.3", + "safe-buffer": "5.1.2", + "websocket-extensions": "0.1.4" } }, "websocket-extensions": { @@ -23441,39 +21976,38 @@ "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==", + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "iconv-lite": "0.4.24" + "isexe": "2.0.0" } }, - "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==", + "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, "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "is-bigint": "1.0.2", + "is-boolean-object": "1.1.1", + "is-number-object": "1.0.5", + "is-string": "1.0.6", + "is-symbol": "1.0.4" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "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, "requires": { - "isexe": "^2.0.0" + "is-map": "2.0.2", + "is-set": "2.0.2", + "is-weakmap": "2.0.1", + "is-weakset": "2.0.1" } }, "which-module": { @@ -23482,13 +22016,28 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "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, + "requires": { + "available-typed-arrays": "1.0.4", + "call-bind": "1.0.2", + "es-abstract": "1.18.3", + "foreach": "2.0.5", + "function-bind": "1.1.1", + "has-symbols": "1.0.2", + "is-typed-array": "1.1.5" + } + }, "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, "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -23509,8 +22058,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -23519,7 +22068,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -23542,25 +22091,30 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "workerpool": { + "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", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "4.3.0", + "string-width": "4.2.2", + "strip-ansi": "6.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==", + "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" + "color-convert": "2.0.1" } }, "color-convert": { @@ -23569,7 +22123,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -23592,42 +22146,24 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "^0.5.1" - } - }, - "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" + "mkdirp": "0.5.5" + }, + "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" + } + } } }, "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", - "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 - }, - "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==", - "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=", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", + "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==", "dev": true }, "xtend": { @@ -23637,9 +22173,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": { @@ -23655,156 +22191,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.2.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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.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" - } - }, - "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==", + "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" - } } } }, @@ -23814,9 +22222,9 @@ "integrity": "sha1-V/RQULgu/VcYKzlzxUqgXLXSUjA=", "dev": true, "requires": { - "cac": "^3.0.3", - "chalk": "^1.1.3", - "cross-spawn": "^4.0.2" + "cac": "3.0.4", + "chalk": "1.1.3", + "cross-spawn": "4.0.2" }, "dependencies": { "ansi-regex": { @@ -23837,11 +22245,11 @@ "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" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "strip-ansi": { @@ -23850,7 +22258,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -23867,26 +22275,32 @@ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "buffer-crc32": "0.2.13", + "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", - "readable-stream": "^3.6.0" + "archiver-utils": "2.1.0", + "compress-commons": "4.1.1", + "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 7fef561be33..769dc0bbbf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "3.27.0-pre", + "version": "5.14.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -28,27 +28,29 @@ "@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", - "documentation": "^5.2.2", + "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": "^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", @@ -56,44 +58,44 @@ "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.4", + "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": "^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", @@ -101,17 +103,15 @@ }, "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", - "deep-equal": "^1.0.1", "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": "1.1.10" + "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/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index f3297412a35..97513b80cc7 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -18,6 +18,8 @@ const { BIDDER_DONE, SET_TARGETING, AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, + AUCTION_DEBUG, ADD_AD_UNITS } } = CONSTANTS; @@ -112,6 +114,8 @@ 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 => { args.config = typeof config === 'object' ? config.options || {} : {}; // enableAnaltyics configuration object diff --git a/src/Renderer.js b/src/Renderer.js index 85bcbb383e8..c997658b30b 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -39,17 +39,22 @@ export function Renderer(options) { // use a function, not an arrow, in order to be able to pass "arguments" through this.render = function () { - if (!isRendererDefinedOnAdUnit(adUnitCode)) { + const renderArgs = arguments + const runRender = () => { + if (this._render) { + this._render.apply(this, renderArgs) + } else { + utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + } + } + + if (!isRendererPreferredFromAdUnit(adUnitCode)) { // we expect to load a renderer url once only so cache the request to load script + 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}`); - } - - if (this._render) { - this._render.apply(this, arguments) // _render is expected to use push as appropriate - } else { - utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + runRender() } }.bind(this) // bind the function to this object to avoid 'this' errors } @@ -110,10 +115,26 @@ export function executeRenderer(renderer, bid) { renderer.render(bid); } -function isRendererDefinedOnAdUnit(adUnitCode) { +function isRendererPreferredFromAdUnit(adUnitCode) { const adUnits = $$PREBID_GLOBAL$$.adUnits; const adUnit = find(adUnits, adUnit => { return adUnit.code === adUnitCode; }); - return !!(adUnit && adUnit.renderer && adUnit.renderer.url && adUnit.renderer.render); + + if (!adUnit) { + return false + } + + // renderer defined at adUnit level + const adUnitRenderer = utils.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 hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) + + return !!( + (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || + (hasValidMediaTypeRenderer && !(mediaTypeRenderer.backupOnly === true)) + ); } diff --git a/src/adapterManager.js b/src/adapterManager.js index 2108bb7a4f6..5f8f2e5721c 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -22,9 +22,11 @@ let adapterManager = {}; let _bidderRegistry = adapterManager.bidderRegistry = {}; let _aliasRegistry = adapterManager.aliasRegistry = {}; -let _s2sConfig = {}; +let _s2sConfigs = []; config.getConfig('s2sConfig', config => { - _s2sConfig = config.s2sConfig; + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } }); var _analyticsRegistry = {}; @@ -66,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' @@ -118,15 +120,15 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) const hookedGetBids = hook('sync', getBids, 'getBids'); -function getAdUnitCopyForPrebidServer(adUnits) { - let adaptersServerSide = _s2sConfig.bidders; +function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { + let adaptersServerSide = s2sConfig.bidders; let adUnitsCopy = utils.deepClone(adUnits); adUnitsCopy.forEach((adUnit) => { // filter out client side bids adUnit.bids = adUnit.bids.filter((bid) => { return includes(adaptersServerSide, bid.bidder) && - (!doingS2STesting() || bid.finalSource !== s2sTestingModule.CLIENT); + (!doingS2STesting(s2sConfig) || bid.finalSource !== s2sTestingModule.CLIENT); }).map((bid) => { bid.bid_id = utils.getUniqueIdentifierStr(); return bid; @@ -145,7 +147,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { // filter out s2s bids adUnitsClientCopy.forEach((adUnit) => { adUnit.bids = adUnit.bids.filter((bid) => { - return !doingS2STesting() || bid.finalSource !== s2sTestingModule.SERVER; + return !clientTestAdapters.length || bid.finalSource !== s2sTestingModule.SERVER; }) }); @@ -177,6 +179,27 @@ export let uspDataHandler = { } }; +export let coppaDataHandler = { + getCoppa: function() { + return !!(config.getConfig('coppa')) + } +}; + +// export for testing +export let clientTestAdapters = []; +export const allS2SBidders = []; + +export function getAllS2SBidders() { + adapterManager.s2STestingEnabled = false; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + allS2SBidders.push(...s2sConfig.bidders); + } + } + }) +} + adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { /** * emit and pass adunits for external modification @@ -184,8 +207,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); - let bidRequests = []; - let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { bidderCodes = shuffle(bidderCodes); @@ -193,70 +214,86 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a const refererInfo = getRefererInfo(); let clientBidderCodes = bidderCodes; - let clientTestAdapters = []; - - if (_s2sConfig.enabled) { - // if s2sConfig.bidderControl testing is turned on - if (doingS2STesting()) { - // get all adapters doing client testing - const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits); - clientTestAdapters = bidderMap[s2sTestingModule.CLIENT]; - } - // these are called on the s2s adapter - let adaptersServerSide = _s2sConfig.bidders; - - // don't call these client side (unless client request is needed for testing) - clientBidderCodes = bidderCodes.filter(elm => - !includes(adaptersServerSide, elm) || includes(clientTestAdapters, elm) - ); + let bidRequests = []; - const adUnitsContainServerRequests = adUnits => Boolean( - find(adUnits, adUnit => find(adUnit.bids, bid => ( - bid.bidSource || - (_s2sConfig.bidderControl && _s2sConfig.bidderControl[bid.bidder]) - ) && bid.finalSource === s2sTestingModule.SERVER)) - ); + if (allS2SBidders.length === 0) { + getAllS2SBidders(); + } - if (isTestingServerOnly() && adUnitsContainServerRequests(adUnits)) { - clientBidderCodes.length = 0; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (doingS2STesting(s2sConfig)) { + s2sTestingModule.calculateBidSources(s2sConfig); + const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits, allS2SBidders); + // get all adapters doing client testing + bidderMap[s2sTestingModule.CLIENT].forEach(bidder => { + if (!includes(clientTestAdapters, bidder)) { + clientTestAdapters.push(bidder); + } + }) + } } + }) - let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits); - let tid = utils.generateUUID(); - adaptersServerSide.forEach(bidderCode => { - const bidderRequestId = utils.getUniqueIdentifierStr(); - const bidderRequest = { - bidderCode, - auctionId, - bidderRequestId, - tid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), - auctionStart: auctionStart, - timeout: _s2sConfig.timeout, - src: CONSTANTS.S2S.SRC, - refererInfo - }; - if (bidderRequest.bids.length !== 0) { - bidRequests.push(bidderRequest); + // don't call these client side (unless client request is needed for testing) + clientBidderCodes = bidderCodes.filter(bidderCode => { + return !includes(allS2SBidders, bidderCode) || includes(clientTestAdapters, bidderCode) + }); + + // these are called on the s2s adapter + let adaptersServerSide = allS2SBidders; + + const adUnitsContainServerRequests = (adUnits, s2sConfig) => Boolean( + find(adUnits, adUnit => find(adUnit.bids, bid => ( + bid.bidSource || + (s2sConfig.bidderControl && s2sConfig.bidderControl[bid.bidder]) + ) && bid.finalSource === s2sTestingModule.SERVER)) + ); + + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if ((isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig))) { + utils.logWarn('testServerOnly: True. All client requests will be suppressed.'); + clientBidderCodes.length = 0; } - }); - // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() - // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs - adUnitsS2SCopy.forEach((adUnitCopy) => { - let validBids = adUnitCopy.bids.filter((adUnitBid) => { - return find(bidRequests, request => { - return find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id); - }); + let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); + let tid = utils.generateUUID(); + adaptersServerSide.forEach(bidderCode => { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + auctionId, + bidderRequestId, + tid, + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + auctionStart: auctionStart, + timeout: s2sConfig.timeout, + src: CONSTANTS.S2S.SRC, + refererInfo + }; + if (bidderRequest.bids.length !== 0) { + bidRequests.push(bidderRequest); + } }); - adUnitCopy.bids = validBids; - }); - bidRequests.forEach(request => { - request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); - }); - } + // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() + // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs + adUnitsS2SCopy.forEach((adUnitCopy) => { + let validBids = adUnitCopy.bids.filter((adUnitBid) => + find(bidRequests, request => + find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id))); + adUnitCopy.bids = validBids; + }); + + bidRequests.forEach(request => { + if (request.adUnitsS2SCopy === undefined) { + request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + } + }); + } + }) // client adapters let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); @@ -306,64 +343,84 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request return partitions; }, [[], []]); - if (serverBidRequests.length) { - // s2s should get the same client side timeout as other client side requests. - const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { - request: requestCallbacks.request.bind(null, 's2s'), - done: requestCallbacks.done - } : undefined); - let adaptersServerSide = _s2sConfig.bidders; - const s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; - let tid = serverBidRequests[0].tid; - let adUnitsS2SCopy = serverBidRequests[0].adUnitsS2SCopy; - - if (s2sAdapter) { - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; - if (s2sBidRequest.ad_units.length) { - let doneCbs = serverBidRequests.map(bidRequest => { - bidRequest.start = timestamp(); - return doneCb.bind(bidRequest); - }); - - // only log adapters that actually have adUnit bids - let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { - return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => { return adapters.concat(bid.bidder) }, [])); - }, []); - utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => { - return includes(allBidders, adapter); - }).join(',')}`); - - // fire BID_REQUESTED event for each s2s bidRequest - serverBidRequests.forEach(bidRequest => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - }); + var uniqueServerBidRequests = []; + serverBidRequests.forEach(serverBidRequest => { + var index = -1; + for (var i = 0; i < uniqueServerBidRequests.length; ++i) { + if (serverBidRequest.tid === uniqueServerBidRequests[i].tid) { + index = i; + break; + } + } + if (index <= -1) { + uniqueServerBidRequests.push(serverBidRequest); + } + }); - // make bid requests - s2sAdapter.callBids( - s2sBidRequest, - serverBidRequests, - function(adUnitCode, bid) { - let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); - if (bidderRequest) { - addBidResponse.call(bidderRequest, adUnitCode, bid) - } - }, - () => doneCbs.forEach(done => done()), - s2sAjax - ); + let counter = 0 + _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. + const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { + request: requestCallbacks.request.bind(null, 's2s'), + done: requestCallbacks.done + } : undefined); + let adaptersServerSide = s2sConfig.bidders; + const s2sAdapter = _bidderRegistry[s2sConfig.adapter]; + let tid = uniqueServerBidRequests[counter].tid; + let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; + + let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.tid === tid) + + if (s2sAdapter) { + let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + if (s2sBidRequest.ad_units.length) { + let doneCbs = uniqueServerRequests.map(bidRequest => { + bidRequest.start = timestamp(); + return doneCb.bind(bidRequest); + }); + + // only log adapters that actually have adUnit bids + 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(',')}`); + + // fire BID_REQUESTED event for each s2s bidRequest + uniqueServerRequests.forEach(bidRequest => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + }); + + // make bid requests + s2sAdapter.callBids( + s2sBidRequest, + serverBidRequests, + (adUnitCode, bid) => { + let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); + if (bidderRequest) { + addBidResponse.call(bidderRequest, adUnitCode, bid) + } + }, + () => doneCbs.forEach(done => done()), + s2sAjax + ); + } + } else { + utils.logError('missing ' + s2sConfig.adapter); } - } else { - utils.logError('missing ' + _s2sConfig.adapter); + counter++ } - } + }); // handle client adapter requests clientBidRequests.forEach(bidRequest => { bidRequest.start = timestamp(); // TODO : Do we check for bid in pool from here and skip calling adapter again ? const adapter = _bidderRegistry[bidRequest.bidderCode]; - utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + config.runWithBidder(bidRequest.bidderCode, () => { + utils.logMessage(`CALLING BIDDER`); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + }); let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { request: requestCallbacks.request.bind(null, bidRequest.bidderCode), done: requestCallbacks.done @@ -390,27 +447,27 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request }); }; -function doingS2STesting() { - return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule; +function doingS2STesting(s2sConfig) { + return s2sConfig && s2sConfig.enabled && s2sConfig.testing && s2sTestingModule; } -function isTestingServerOnly() { - return Boolean(doingS2STesting() && _s2sConfig.testServerOnly); +function isTestingServerOnly(s2sConfig) { + return Boolean(doingS2STesting(s2sConfig) && s2sConfig.testServerOnly); }; function getSupportedMediaTypes(bidderCode) { - let result = []; - if (includes(adapterManager.videoAdapters, bidderCode)) result.push('video'); - if (includes(nativeAdapters, bidderCode)) result.push('native'); - return result; + let supportedMediaTypes = []; + if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); + if (includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); + return supportedMediaTypes; } adapterManager.videoAdapters = []; // added by adapterLoader for now -adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supportedMediaTypes = []} = {}) { - if (bidAdaptor && bidderCode) { - if (typeof bidAdaptor.callBids === 'function') { - _bidderRegistry[bidderCode] = bidAdaptor; +adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supportedMediaTypes = []} = {}) { + if (bidAdapter && bidderCode) { + if (typeof bidAdapter.callBids === 'function') { + _bidderRegistry[bidderCode] = bidAdapter; if (includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); @@ -422,37 +479,45 @@ adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supported utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); } } else { - utils.logError('bidAdaptor or bidderCode not specified'); + utils.logError('bidAdapter or bidderCode not specified'); } }; -adapterManager.aliasBidAdapter = function (bidderCode, alias) { +adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { let existingAlias = _bidderRegistry[alias]; if (typeof existingAlias === 'undefined') { - let bidAdaptor = _bidderRegistry[bidderCode]; - if (typeof bidAdaptor === 'undefined') { + let bidAdapter = _bidderRegistry[bidderCode]; + if (typeof bidAdapter === 'undefined') { // check if alias is part of s2sConfig and allow them to register if so (as base bidder may be s2s-only) - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - - if (!(s2sBidders && includes(s2sBidders, alias))) { + const nonS2SAlias = []; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + const s2sBidders = s2sConfig && s2sConfig.bidders; + if (!(s2sConfig && includes(s2sBidders, alias))) { + nonS2SAlias.push(bidderCode); + } else { + _aliasRegistry[alias] = bidderCode; + } + } + }); + nonS2SAlias.forEach(bidderCode => { utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); - } else { - _aliasRegistry[alias] = bidderCode; - } + }) } else { try { let newAdapter; let supportedMediaTypes = getSupportedMediaTypes(bidderCode); // Have kept old code to support backward compatibilitiy. // Remove this if loop when all adapters are supporting bidderFactory. i.e When Prebid.js is 1.0 - if (bidAdaptor.constructor.prototype != Object.prototype) { - newAdapter = new bidAdaptor.constructor(); + if (bidAdapter.constructor.prototype != Object.prototype) { + newAdapter = new bidAdapter.constructor(); newAdapter.setBidderCode(alias); } else { - let spec = bidAdaptor.getSpec(); - newAdapter = newBidder(Object.assign({}, spec, { code: alias })); + let spec = bidAdapter.getSpec(); + let gvlid = options && options.gvlid; + let skipPbsAliasing = options && options.skipPbsAliasing; + newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid, skipPbsAliasing })); _aliasRegistry[alias] = bidderCode; } adapterManager.registerBidAdapter(newAdapter, alias, { @@ -467,11 +532,11 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias) { } }; -adapterManager.registerAnalyticsAdapter = function ({adapter, code}) { +adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { if (adapter && code) { if (typeof adapter.enableAnalytics === 'function') { adapter.code = code; - _analyticsRegistry[code] = adapter; + _analyticsRegistry[code] = { adapter, gvlid }; } else { utils.logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); @@ -487,7 +552,7 @@ adapterManager.enableAnalytics = function (config) { } utils._each(config, adapterConfig => { - var adapter = _analyticsRegistry[adapterConfig.provider]; + var adapter = _analyticsRegistry[adapterConfig.provider].adapter; if (adapter) { adapter.enableAnalytics(adapterConfig); } else { @@ -495,12 +560,16 @@ adapterManager.enableAnalytics = function (config) { ${adapterConfig.provider}.`); } }); -}; +} adapterManager.getBidAdapter = function(bidder) { return _bidderRegistry[bidder]; }; +adapterManager.getAnalyticsAdapter = function(code) { + return _analyticsRegistry[code]; +} + // the s2sTesting module is injected when it's loaded rather than being imported // importing it causes the packager to include it even when it's not explicitly included in the build export function setS2STestingModule(module) { @@ -545,4 +614,8 @@ adapterManager.callSetTargetingBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onSetTargeting', bid); }; +adapterManager.callBidViewableBidder = function(bidder, bid) { + tryCallBidderMethod(bidder, 'onBidViewable', bid); +}; + export default adapterManager; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 8dd351563be..fa0ad36e7ff 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -9,7 +9,7 @@ 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 } from '../utils.js'; +import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, 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'; @@ -153,8 +153,16 @@ export function registerBidder(spec) { putBidder(spec); if (Array.isArray(spec.aliases)) { spec.aliases.forEach(alias => { - adapterManager.aliasRegistry[alias] = spec.code; - putBidder(Object.assign({}, spec, { code: alias })); + let aliasCode = alias; + let gvlid; + let skipPbsAliasing; + if (isPlainObject(alias)) { + aliasCode = alias.code; + gvlid = alias.gvlid; + skipPbsAliasing = alias.skipPbsAliasing + } + adapterManager.aliasRegistry[aliasCode] = spec.code; + putBidder(Object.assign({}, spec, { code: aliasCode, gvlid, skipPbsAliasing })); }); } } @@ -189,8 +197,10 @@ export function newBidder(spec) { const responses = []; function afterAllResponses() { done(); - events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); - registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); + config.runWithBidder(spec.code, () => { + events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); + registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); + }); } const validBidRequests = bidderRequest.bids.filter(filterAndWarn); @@ -220,6 +230,7 @@ export function newBidder(spec) { // 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(_ => events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, bidderRequest)); requests.forEach(processRequest); function formatGetParameters(data) { @@ -267,8 +278,8 @@ export function newBidder(spec) { } // 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(). + // 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) { onTimelyResponse(spec.code); @@ -307,6 +318,7 @@ export function newBidder(spec) { // 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 { diff --git a/src/adloader.js b/src/adloader.js index 1c18ce82b16..5460cc79410 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import * as utils from './utils.js'; const _requestCache = {}; // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + 'adloox', 'criteo', 'outstream', 'adagio', diff --git a/src/ajax.js b/src/ajax.js index b1e4cbdbdff..a6317e7963d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -66,7 +66,7 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { } 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; @@ -94,6 +94,7 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { } } catch (error) { utils.logError('xhr construction', error); + typeof callback === 'object' && callback !== null && callback.error(error); } } } diff --git a/src/auction.js b/src/auction.js index b82b4752479..a6f0342e582 100644 --- a/src/auction.js +++ b/src/auction.js @@ -66,6 +66,7 @@ import { config } from './config.js'; import { userSync } from './userSync.js'; import { hook } from './hook.js'; 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'; @@ -196,6 +197,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); _auctionStatus = AUCTION_COMPLETED; @@ -397,10 +399,19 @@ export function auctionCallbacks(auctionDone, auctionInstance) { function adapterDone() { let bidderRequest = this; + let bidderRequests = auctionInstance.getBidRequests(); + const auctionOptionsConfig = config.getConfig('auctionOptions'); bidderRequestsDone.add(bidderRequest); - allAdapterCalledDone = auctionInstance.getBidRequests() - .every(bidderRequest => bidderRequestsDone.has(bidderRequest)); + + if (auctionOptionsConfig && !utils.isEmpty(auctionOptionsConfig)) { + const secondaryBidders = auctionOptionsConfig.secondaryBidders; + if (secondaryBidders && !bidderRequests.every(bidder => includes(secondaryBidders, bidder.bidderCode))) { + bidderRequests = bidderRequests.filter(request => !includes(secondaryBidders, request.bidderCode)); + } + } + + allAdapterCalledDone = bidderRequests.every(bidderRequest => bidderRequestsDone.has(bidderRequest)); bidderRequest.bids.forEach(bid => { if (!bidResponseMap[bid.bidId]) { @@ -442,13 +453,13 @@ export function addBidToAuction(auctionInstance, bidResponse) { function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded) { let addBid = true; - const bidderRequest = getBidRequest(bidResponse.requestId, [bidRequests]); + const bidderRequest = getBidRequest(bidResponse.originalRequestId || bidResponse.requestId, [bidRequests]); const videoMediaType = bidderRequest && deepAccess(bidderRequest, 'mediaTypes.video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); if (config.getConfig('cache.url') && context !== OUTSTREAM) { - if (!bidResponse.videoCacheKey) { + if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; callPrebidCache(auctionInstance, bidResponse, afterBidAdded, bidderRequest); } else if (!bidResponse.vastUrl) { @@ -509,12 +520,29 @@ 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); + const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId); const adUnitRenderer = bidReq && bidReq.renderer; - if (adUnitRenderer && adUnitRenderer.url) { - bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); - bidObject.renderer.setRender(adUnitRenderer.render); + // a publisher can also define a renderer for a mediaType + const bidObjectMediaType = bidObject.mediaType; + const bidMediaType = bidReq && + bidReq.mediaTypes && + bidReq.mediaTypes[bidObjectMediaType]; + + var mediaTypeRenderer = bidMediaType && bidMediaType.renderer; + + var renderer = null; + + // the renderer for the mediaType takes precendence + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { + renderer = mediaTypeRenderer; + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + renderer = adUnitRenderer; + } + + if (renderer) { + bidObject.renderer = Renderer.install({ url: renderer.url }); + bidObject.renderer.setRender(renderer.render); } // Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket' @@ -599,6 +627,16 @@ export const getPriceByGranularity = (granularity) => { } } +/** + * This function returns a function to get first advertiser domain from bid response meta + * @returns {function} + */ +export const getAdvertiserDomain = () => { + return (bid) => { + return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? bid.meta.advertiserDomains[0] : ''; + } +} + /** * @param {string} mediaType * @param {string} bidderCode @@ -635,6 +673,7 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), createKeyVal(TARGETING_KEYS.SOURCE, 'source'), createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), + createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()), ] } diff --git a/src/auctionManager.js b/src/auctionManager.js index 3d4bd0afe99..bbafd7426d5 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -9,11 +9,14 @@ * * @property {function(): Array} getBidsRequested - returns consolidated bid requests * @property {function(): Array} getBidsReceived - returns consolidated bid received + * @property {function(): Array} getAllBidsForAdUnitCode - returns consolidated bid received for a given adUnit * @property {function(): Array} getAdUnits - returns consolidated adUnits * @property {function(): Array} getAdUnitCodes - returns consolidated adUnitCodes * @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'; @@ -66,6 +69,13 @@ export function newAuctionManager() { .filter(bid => bid); }; + auctionManager.getAllBidsForAdUnitCode = function(adUnitCode) { + return _auctions.map((auction) => { + return auction.getBidsReceived(); + }).reduce(flatten, []) + .filter(bid => bid && bid.adUnitCode === adUnitCode) + }; + auctionManager.getAdUnits = function() { return _auctions.map(auction => auction.getAdUnits()) .reduce(flatten, []); @@ -105,6 +115,10 @@ export function newAuctionManager() { return _auctions.length && _auctions[_auctions.length - 1].getAuctionId() }; + auctionManager.clearAllAuctions = function() { + _auctions.length = 0; + } + function _addAuction(auction) { _auctions.push(auction); } diff --git a/src/config.js b/src/config.js index 3284be52296..c05a1d9e2a9 100644 --- a/src/config.js +++ b/src/config.js @@ -16,7 +16,7 @@ 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 } from './utils.js'; const from = require('core-js-pure/features/array/from.js'); const utils = require('./utils.js'); @@ -29,6 +29,7 @@ const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; const DEFAULT_DEVICE_ACCESS = true; +const DEFAULT_MAX_NESTED_IFRAMES = 10; const DEFAULT_TIMEOUTBUFFER = 400; @@ -199,6 +200,25 @@ export function newConfig() { set disableAjaxTimeout(val) { this._disableAjaxTimeout = val; }, + + // default max nested iframes for referer detection + _maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, + get maxNestedIframes() { + return this._maxNestedIframes; + }, + set maxNestedIframes(val) { + this._maxNestedIframes = val; + }, + + _auctionOptions: {}, + get auctionOptions() { + return this._auctionOptions; + }, + set auctionOptions(val) { + if (validateauctionOptions(val)) { + this._auctionOptions = val; + } + }, }; if (config) { @@ -237,6 +257,35 @@ export function newConfig() { } return true; } + + function validateauctionOptions(val) { + if (!utils.isPlainObject(val)) { + utils.logWarn('Auction Options must be an object') + return false + } + + for (let k of Object.keys(val)) { + if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') { + utils.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`); + return false + } else if (!val[k].every(utils.isStr)) { + utils.logWarn(`Auction Options ${k} must be only string`); + return false + } + } else if (k === 'suppressStaleRender') { + if (!utils.isBoolean(val[k])) { + utils.logWarn(`Auction Options ${k} must be of type boolean`); + return false; + } + } + } + return true; + } } /** @@ -266,6 +315,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 ? utils.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 @@ -290,6 +359,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + 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]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + 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]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + 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]}}); + } 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]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -304,13 +486,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -403,11 +586,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -435,7 +620,7 @@ export function newConfig() { try { return fn(); } finally { - currBidder = null; + resetBidder(); } } function callbackWithBidder(bidder) { @@ -454,18 +639,27 @@ export function newConfig() { return currBidder; } + function resetBidder() { + currBidder = null; + } + resetConfig(); return { getCurrentBidder, + resetBidder, getConfig, + readConfig, setConfig, setDefaults, resetConfig, runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/constants.json b/src/constants.json index 5965d77a6c4..114cedf112b 100644 --- a/src/constants.json +++ b/src/constants.json @@ -34,12 +34,18 @@ "BIDDER_DONE": "bidderDone", "SET_TARGETING": "setTargeting", "BEFORE_REQUEST_BIDS": "beforeRequestBids", + "BEFORE_BIDDER_HTTP": "beforeBidderHttp", "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", - "AD_RENDER_FAILED" : "adRenderFailed" + "AD_RENDER_FAILED": "adRenderFailed", + "AD_RENDER_SUCCEEDED": "adRenderSucceeded", + "TCF2_ENFORCEMENT": "tcf2Enforcement", + "AUCTION_DEBUG": "auctionDebug", + "BID_VIEWABLE": "bidViewable", + "STALE_RENDER": "staleRender" }, "AD_RENDER_FAILED_REASON" : { - "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", + "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", "NO_AD": "noAd", "EXCEPTION": "exception", "CANNOT_FIND_AD": "cannotFindAd", @@ -66,6 +72,17 @@ "FORMAT": "hb_format", "UUID": "hb_uuid", "CACHE_ID": "hb_cache_id", + "CACHE_HOST": "hb_cache_host", + "ADOMAIN" : "hb_adomain" + }, + "DEFAULT_TARGETING_KEYS": { + "BIDDER": "hb_bidder", + "AD_ID": "hb_adid", + "PRICE_BUCKET": "hb_pb", + "SIZE": "hb_size", + "DEAL": "hb_deal", + "FORMAT": "hb_format", + "UUID": "hb_uuid", "CACHE_HOST": "hb_cache_host" }, "NATIVE_KEYS": { @@ -86,7 +103,9 @@ "likes": "hb_native_likes", "phone": "hb_native_phone", "price": "hb_native_price", - "salePrice": "hb_native_saleprice" + "salePrice": "hb_native_saleprice", + "rendererUrl": "hb_renderer_url", + "adTemplate": "hb_adTemplate" }, "S2S" : { "SRC" : "s2s", diff --git a/src/events.js b/src/events.js index e7a11635476..8749ddf206b 100644 --- a/src/events.js +++ b/src/events.js @@ -44,7 +44,8 @@ module.exports = (function () { eventsFired.push({ eventType: eventString, args: eventPayload, - id: key + id: key, + elapsedTime: utils.getPerformanceNow(), }); /** Push each specific callback to the `callbacks` array. diff --git a/src/native.js b/src/native.js index e41d7740ffa..7596b38534d 100644 --- a/src/native.js +++ b/src/native.js @@ -77,18 +77,6 @@ export function nativeBidIsValid(bid, bidRequests) { return false; } - if (deepAccess(bid, 'native.image')) { - if (!deepAccess(bid, 'native.image.height') || !deepAccess(bid, 'native.image.width')) { - return false; - } - } - - if (deepAccess(bid, 'native.icon')) { - if (!deepAccess(bid, 'native.icon.height') || !deepAccess(bid, 'native.icon.width')) { - return false; - } - } - const requestedAssets = bidRequest.nativeParams; if (!requestedAssets) { return true; @@ -154,21 +142,48 @@ export function fireNativeTrackers(message, adObject) { export function getNativeTargeting(bid, bidReq) { let keyValues = {}; - Object.keys(bid['native']).forEach(asset => { - const key = CONSTANTS.NATIVE_KEYS[asset]; - let value = getAssetValue(bid['native'][asset]); + 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 globalSendTargetingKeys = deepAccess( + bidReq, + `nativeParams.sendTargetingKeys` + ) !== false; + + const nativeKeys = getNativeKeys(bidReq); - const sendPlaceholder = deepAccess( - bidReq, - `mediaTypes.native.${asset}.sendId` - ); + const flatBidNativeKeys = { ...bid.native, ...bid.native.ext }; + delete flatBidNativeKeys.ext; + + Object.keys(flatBidNativeKeys).forEach(asset => { + const key = nativeKeys[asset]; + let value = getAssetValue(bid.native[asset]) || getAssetValue(deepAccess(bid, `native.ext.${asset}`)); + + if (asset === 'adTemplate' || !key || !value) { + return; + } + + let sendPlaceholder = deepAccess(bidReq, `nativeParams.${asset}.sendId`); + if (typeof sendPlaceholder !== 'boolean') { + sendPlaceholder = deepAccess(bidReq, `nativeParams.ext.${asset}.sendId`); + } if (sendPlaceholder) { const placeholder = `${key}:${bid.adId}`; value = placeholder; } - if (key && value) { + let assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.${asset}.sendTargetingKeys`) + if (typeof assetSendTargetingKeys !== 'boolean') { + assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.ext.${asset}.sendTargetingKeys`); + } + + const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; + + if (sendTargeting) { keyValues[key] = value; } }); @@ -187,6 +202,12 @@ export function getAssetMessage(data, adObject) { assets: [], }; + if (adObject.native.hasOwnProperty('adTemplate')) { + message.adTemplate = getAssetValue(adObject.native['adTemplate']); + } if (adObject.native.hasOwnProperty('rendererUrl')) { + message.rendererUrl = getAssetValue(adObject.native['rendererUrl']); + } + data.assets.forEach(asset => { const key = getKeyByValue(CONSTANTS.NATIVE_KEYS, asset); const value = getAssetValue(adObject.native[key]); @@ -197,6 +218,35 @@ export function getAssetMessage(data, adObject) { return message; } +export function getAllAssetsMessage(data, adObject) { + const message = { + message: 'assetResponse', + adId: data.adId, + assets: [] + }; + + Object.keys(adObject.native).forEach(function(key, index) { + if (key === 'adTemplate' && adObject.native[key]) { + message.adTemplate = getAssetValue(adObject.native[key]); + } else if (key === 'rendererUrl' && adObject.native[key]) { + message.rendererUrl = getAssetValue(adObject.native[key]); + } else if (key === 'ext') { + Object.keys(adObject.native[key]).forEach(extKey => { + if (adObject.native[key][extKey]) { + const value = getAssetValue(adObject.native[key][extKey]); + message.assets.push({ key: extKey, value }); + } + }) + } else if (adObject.native[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { + const value = getAssetValue(adObject.native[key]); + + message.assets.push({ key, value }); + } + }); + + return message; +} + /** * Native assets can be a string or an object with a url prop. Returns the value * appropriate for sending in adserver targeting or placeholder replacement. @@ -208,3 +258,18 @@ function getAssetValue(value) { return value; } + +function getNativeKeys(bidReq) { + const extraNativeKeys = {} + + if (deepAccess(bidReq, 'nativeParams.ext')) { + Object.keys(bidReq.nativeParams.ext).forEach(extKey => { + extraNativeKeys[extKey] = `hb_native_${extKey}`; + }) + } + + return { + ...CONSTANTS.NATIVE_KEYS, + ...extraNativeKeys + } +} diff --git a/src/prebid.js b/src/prebid.js index 1710849ba92..31fb1ac410e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,17 +1,17 @@ /** @module pbjs */ import { getGlobal } from './prebidGlobal.js'; -import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, isArrayOfNums } from './utils.js'; +import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; -import { targeting } from './targeting.js'; +import { filters, targeting } from './targeting.js'; import { hook } from './hook.js'; import { sessionLoader } from './debugging.js'; import includes from 'core-js-pure/features/array/includes.js'; import { adunitCounter } from './adUnits.js'; -import { isRendererRequired, executeRenderer } from './Renderer.js'; +import { executeRenderer, isRendererRequired } from './Renderer.js'; import { createBid } from './bidfactory.js'; import { storageCallbacks } from './storageManager.js'; @@ -23,7 +23,7 @@ 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, AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, 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 = { @@ -43,6 +43,9 @@ $$PREBID_GLOBAL$$.libLoaded = true; $$PREBID_GLOBAL$$.version = 'v$prebid.version$'; utils.logInfo('Prebid.js v$prebid.version$ loaded'); +// modules list generated from build +$$PREBID_GLOBAL$$.installedModules = ['v$prebid.modulesList$']; + // create adUnit array $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; @@ -83,50 +86,72 @@ function validateSizes(sizes, targLength) { } function validateBannerMediaType(adUnit) { - const banner = adUnit.mediaTypes.banner; + const validatedAdUnit = utils.deepClone(adUnit); + const banner = validatedAdUnit.mediaTypes.banner; const bannerSizes = validateSizes(banner.sizes); if (bannerSizes.length > 0) { banner.sizes = bannerSizes; // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes - adUnit.sizes = bannerSizes; + 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.'); - delete adUnit.mediaTypes.banner + delete validatedAdUnit.mediaTypes.banner } + return validatedAdUnit; } function validateVideoMediaType(adUnit) { - const video = adUnit.mediaTypes.video; - let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; - - 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.'); + const validatedAdUnit = utils.deepClone(adUnit); + const video = validatedAdUnit.mediaTypes.video; + if (video.playerSize) { + let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; + + 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.'); + } + 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.'); + delete validatedAdUnit.mediaTypes.video.playerSize; } - video.playerSize = videoSizes; - // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize - adUnit.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.'); - delete adUnit.mediaTypes.video.playerSize; } + return validatedAdUnit; } function validateNativeMediaType(adUnit) { - const native = adUnit.mediaTypes.native; + const validatedAdUnit = utils.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.'); - delete adUnit.mediaTypes.native.image.sizes; + 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.'); - delete adUnit.mediaTypes.native.image.aspect_ratios; + 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.'); - delete adUnit.mediaTypes.native.icon.sizes; + delete validatedAdUnit.mediaTypes.native.icon.sizes; + } + return validatedAdUnit; +} + +function validateAdUnitPos(adUnit, mediaType) { + let pos = utils.deepAccess(adUnit, `mediaTypes.${mediaType}.pos`); + + if (!pos || !utils.isNumber(pos) || !isFinite(pos)) { + let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`; + + utils.logWarn(warning); + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: warning}); + delete adUnit.mediaTypes[mediaType].pos; } + + return adUnit } export const adUnitSetupChecks = { @@ -137,29 +162,43 @@ export const adUnitSetupChecks = { }; export const checkAdUnitSetup = hook('sync', function (adUnits) { - return adUnits.filter(adUnit => { + const validatedAdUnits = []; + + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + 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.`); + 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.`); - return false; + return; } if (mediaTypes.banner) { - validateBannerMediaType(adUnit); + validatedBanner = validateBannerMediaType(adUnit); + if (mediaTypes.banner.hasOwnProperty('pos')) validatedBanner = validateAdUnitPos(validatedBanner, 'banner'); } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - validateVideoMediaType(adUnit); - } + validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); + if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } if (mediaTypes.native) { - validateNativeMediaType(adUnit); + validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } - return true; + + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + + validatedAdUnits.push(validatedAdUnit); }); + + return validatedAdUnits; }, 'checkAdUnitSetup'); /// /////////////////////////////// @@ -186,13 +225,31 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { } }; +/** + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode + * @returns {Object} returnObj return bid + */ +$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { + if (adunitCode) { + const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) + .filter(filters.isUnusedBid) + .filter(filters.isBidNotExpired) + + return bid.length ? bid.reduce(getHighestCpm) : {} + } else { + utils.logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); + } +}; + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { +$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; }; @@ -238,6 +295,18 @@ $$PREBID_GLOBAL$$.getNoBids = function () { return getBids('getNoBids'); }; +/** + * This function returns the bids requests involved in an auction but not bid on or the specified adUnitCode + * @param {string} adUnitCode adUnitCode + * @alias module:pbjs.getNoBidsForAdUnitCode + * @return {Object} bidResponse object + */ + +$$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { + const bids = auctionManager.getNoBids().filter(bid => bid.adUnitCode === adUnitCode); + return { bids }; +}; + /** * This function returns the bid responses at the given moment. * @alias module:pbjs.getBidResponses @@ -300,7 +369,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function(adUnitCodes) { +$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { utils.logError('window.apntag is not defined on the page'); @@ -322,6 +391,14 @@ function emitAdRenderFail({ reason, message, bid, id }) { events.emit(AD_RENDER_FAILED, data); } +function emitAdRenderSucceeded({ doc, bid, id }) { + const data = { doc }; + if (bid) data.bid = bid; + if (id) data.adId = id; + + events.emit(AD_RENDER_SUCCEEDED, data); +} + /** * 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 @@ -329,7 +406,7 @@ function emitAdRenderFail({ reason, message, bid, id }) { * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -$$PREBID_GLOBAL$$.renderAd = function (doc, id) { +$$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); utils.logMessage('Calling renderAd with adId :' + id); @@ -337,55 +414,80 @@ $$PREBID_GLOBAL$$.renderAd = function (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); - // 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'); + let shouldRender = true; + if (bid && bid.status === CONSTANTS.BID_STATUS.RENDERED) { + utils.logWarn(`Ad id ${bid.adId} has been rendered before`); + events.emit(STALE_RENDER, bid); + if (utils.deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + shouldRender = false; + } + } + + if (shouldRender) { + // 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); + } + + // 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`); + + if (isRendererRequired(renderer)) { + executeRenderer(renderer, bid); + utils.insertElement(creativeComment, doc, 'html'); + emitAdRenderSucceeded({ doc, bid, id }); + } 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'); + } } + doc.write(ad); + doc.close(); + setRenderSize(doc, width, height); + utils.insertElement(creativeComment, doc, 'html'); + utils.callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); + } 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.insertElement(creativeComment, doc, 'html'); + utils.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}`; @@ -399,7 +501,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { 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 @@ -444,10 +546,24 @@ $$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 || $$PREBID_GLOBAL$$.adUnits; + adUnits = (adUnits && config.convertAdUnitFpd(utils.isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + let _s2sConfigs = []; + const s2sBidders = []; + config.getConfig('s2sConfig', config => { + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } + }); + + _s2sConfigs.forEach(s2sConfig => { + s2sBidders.push(...s2sConfig.bidders); + }); + + adUnits = checkAdUnitSetup(adUnits); + if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -456,8 +572,6 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - adUnits = checkAdUnitSetup(adUnits); - /* * for a given adunit which supports a set of mediaTypes * and a given bidder which supports a set of mediaTypes @@ -466,17 +580,13 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo */ adUnits.forEach(adUnit => { // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present - const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); // get the bidder's mediaTypes const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - const bidders = (s2sBidders) ? allBidders.filter(bidder => { - return !includes(s2sBidders, bidder); - }) : allBidders; + const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; adUnit.transactionId = utils.generateUUID(); @@ -512,7 +622,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo return; } - const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId}); + const auction = auctionManager.createAuction({ adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId }); let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { @@ -523,9 +633,11 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo auction.callBids(); }); -export function executeStorageCallbacks(fn, reqBidsConfigObj) { +export function executeCallbacks(fn, reqBidsConfigObj) { runAll(storageCallbacks); + runAll(enableAnalyticsCallbacks); fn.call(this, reqBidsConfigObj); + function runAll(queue) { var queued; while ((queued = queue.shift())) { @@ -535,7 +647,7 @@ export function executeStorageCallbacks(fn, reqBidsConfigObj) { } // This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not -$$PREBID_GLOBAL$$.requestBids.before(executeStorageCallbacks, 49); +$$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); /** * @@ -545,11 +657,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeStorageCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; @@ -600,6 +708,16 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { events.off(event, handler, id); }; +/** + * Return a copy of all events emitted + * + * @alias module:pbjs.getEvents + */ +$$PREBID_GLOBAL$$.getEvents = function () { + utils.logInfo('Invoking $$PREBID_GLOBAL$$.getEvents'); + return events.getEvents(); +}; + /* * Wrapper to register bidderAdapter externally (adapterManager.registerBidAdapter()) * @param {Function} bidderAdaptor [description] @@ -654,22 +772,30 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { * @param {Object} config.options The options for this particular analytics adapter. This will likely vary between adapters. * @alias module:pbjs.enableAnalytics */ -$$PREBID_GLOBAL$$.enableAnalytics = function (config) { + +// Stores 'enableAnalytics' callbacks for later execution. +const enableAnalyticsCallbacks = []; + +const enableAnalyticsCb = hook('async', function (config) { if (config && !utils.isEmpty(config)) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.enableAnalytics for: ', config); adapterManager.enableAnalytics(config); } else { utils.logError('$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'); } +}, 'enableAnalyticsCb'); + +$$PREBID_GLOBAL$$.enableAnalytics = function (config) { + enableAnalyticsCallbacks.push(enableAnalyticsCb.bind(this, config)); }; /** * @alias module:pbjs.aliasBidder */ -$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { +$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { - adapterManager.aliasBidAdapter(bidderCode, alias); + adapterManager.aliasBidAdapter(bidderCode, alias, options); } else { utils.logError('bidderCode and alias must be passed as arguments', '$$PREBID_GLOBAL$$.aliasBidder'); } @@ -708,12 +834,12 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { * @property {string} adserverTargeting.hb_adid The ad ID of the creative, as understood by the ad server. * @property {string} adserverTargeting.hb_pb The price paid to show the creative, as logged in the ad server. * @property {string} adserverTargeting.hb_bidder The winning bidder whose ad creative will be served by the ad server. -*/ + */ /** * Get all of the bids that have been rendered. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html). * @return {Array} A list of bids that have been rendered. -*/ + */ $$PREBID_GLOBAL$$.getAllWinningBids = function () { return auctionManager.getAllWinningBids(); }; @@ -745,7 +871,7 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { * @property {string} adId The id representing the ad we want to mark * * @alias module:pbjs.markWinningBidAsUsed -*/ + */ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { let bids = []; @@ -757,7 +883,7 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { } else if (markBidRequest.adId) { bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); } else { - utils.logWarn('Inproper usage of markWinningBidAsUsed. It\'ll need an adUnitCode and/or adId to function.'); + utils.logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); } if (bids.length > 0) { @@ -771,6 +897,7 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { * @alias module:pbjs.getConfig */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; +$$PREBID_GLOBAL$$.readConfig = config.readConfig; /** * Set Prebid config options. @@ -843,7 +970,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function(command) { +$$PREBID_GLOBAL$$.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -858,7 +985,7 @@ $$PREBID_GLOBAL$$.cmd.push = function(command) { $$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; function processQueue(queue) { - queue.forEach(function(cmd) { + queue.forEach(function (cmd) { if (typeof cmd.called === 'undefined') { try { cmd.call(); @@ -873,7 +1000,7 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function() { +$$PREBID_GLOBAL$$.processQueue = function () { hook.ready(); processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); diff --git a/src/refererDetection.js b/src/refererDetection.js index 60198678666..7e9f2a7e6c7 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -3,160 +3,60 @@ * The information that it tries to collect includes: * The detected top url in the nav bar, * Whether it was able to reach the top most window (if for example it was embedded in several iframes), - * The number of iframes it was embedded in if applicable, + * The number of iframes it was embedded in if applicable (by default max ten iframes), * A list of the domains of each embedded window if applicable. * Canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage */ +import { config } from './config.js'; import { logWarn } from './utils.js'; +/** + * @param {Window} win Window + * @returns {Function} + */ export function detectReferer(win) { - /** - * Returns number of frames to reach top from current frame where prebid.js sits - * @returns {Array} levels - */ - function getLevels() { - let levels = walkUpWindows(); - let ancestors = getAncestorOrigins(); - - if (ancestors) { - for (let i = 0, l = ancestors.length; i < l; i++) { - levels[i].ancestor = ancestors[i]; - } - } - return levels; - } - /** * This function would return a read-only array of hostnames for all the parent frames. * win.location.ancestorOrigins is only supported in webkit browsers. For non-webkit browsers it will return undefined. + * + * @param {Window} win Window object * @returns {(undefined|Array)} Ancestor origins or undefined */ - function getAncestorOrigins() { + function getAncestorOrigins(win) { try { if (!win.location.ancestorOrigins) { return; } + return win.location.ancestorOrigins; } catch (e) { // Ignore error } } - /** - * This function would try to get referer and urls for all parent frames in case of win.location.ancestorOrigins undefined. - * @param {Array} levels - * @returns {Object} urls for all parent frames and top most detected referer url - */ - function getPubUrlStack(levels) { - let stack = []; - let defUrl = null; - let frameLocation = null; - let prevFrame = null; - let prevRef = null; - let ancestor = null; - let detectedRefererUrl = null; - - let i; - for (i = levels.length - 1; i >= 0; i--) { - try { - frameLocation = levels[i].location; - } catch (e) { - // Ignore error - } - - if (frameLocation) { - stack.push(frameLocation); - if (!detectedRefererUrl) { - detectedRefererUrl = frameLocation; - } - } else if (i !== 0) { - prevFrame = levels[i - 1]; - try { - prevRef = prevFrame.referrer; - ancestor = prevFrame.ancestor; - } catch (e) { - // Ignore error - } - - if (prevRef) { - stack.push(prevRef); - if (!detectedRefererUrl) { - detectedRefererUrl = prevRef; - } - } else if (ancestor) { - stack.push(ancestor); - if (!detectedRefererUrl) { - detectedRefererUrl = ancestor; - } - } else { - stack.push(defUrl); - } - } else { - stack.push(defUrl); - } - } - return { - stack, - detectedRefererUrl - }; - } - /** * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage + * * @param {Object} doc document + * @returns {string|null} */ function getCanonicalUrl(doc) { + let pageURL = config.getConfig('pageUrl'); + + if (pageURL) return pageURL; + try { - let element = doc.querySelector("link[rel='canonical']"); + const element = doc.querySelector("link[rel='canonical']"); + if (element !== null) { return element.href; } } catch (e) { + // Ignore error } - return null; - } - /** - * Walk up to the top of the window to detect origin, number of iframes, ancestor origins and canonical url - */ - function walkUpWindows() { - let acc = []; - let currentWindow; - do { - try { - currentWindow = currentWindow ? currentWindow.parent : win; - try { - let isTop = (currentWindow == win.top); - let refData = { - referrer: currentWindow.document.referrer || null, - location: currentWindow.location.href || null, - isTop - } - if (isTop) { - refData = Object.assign(refData, { - canonicalUrl: getCanonicalUrl(currentWindow.document) - }) - } - acc.push(refData); - } catch (e) { - acc.push({ - referrer: null, - location: null, - isTop: (currentWindow == win.top) - }); - logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); - } - } catch (e) { - acc.push({ - referrer: null, - location: null, - isTop: false - }); - return acc; - } - } while (currentWindow != win.top); - return acc; + return null; } /** @@ -170,31 +70,115 @@ export function detectReferer(win) { */ /** - * Get referer info + * Walk up the windows to get the origin stack and best available referrer, canonical URL, etc. + * * @returns {refererInfo} */ function refererInfo() { - try { - let levels = getLevels(); - let numIframes = levels.length - 1; - let reachedTop = (levels[numIframes].location !== null || - (numIframes > 0 && levels[numIframes - 1].referrer !== null)); - let stackInfo = getPubUrlStack(levels); - let canonicalUrl; - if (levels[levels.length - 1].canonicalUrl) { - canonicalUrl = levels[levels.length - 1].canonicalUrl; + const stack = []; + const ancestors = getAncestorOrigins(win); + const maxNestedIframes = config.getConfig('maxNestedIframes'); + let currentWindow; + let bestReferrer; + let bestCanonicalUrl; + let reachedTop = false; + let level = 0; + let valuesFromAmp = false; + let inAmpFrame = false; + + do { + const previousWindow = currentWindow; + const wasInAmpFrame = inAmpFrame; + let currentLocation; + let crossOrigin = false; + let foundReferrer = null; + + inAmpFrame = false; + currentWindow = currentWindow ? currentWindow.parent : win; + + try { + currentLocation = currentWindow.location.href || null; + } catch (e) { + crossOrigin = true; } - return { - referer: stackInfo.detectedRefererUrl, - reachedTop, - numIframes, - stack: stackInfo.stack, - canonicalUrl - }; - } catch (e) { - // Ignore error - } + if (crossOrigin) { + if (wasInAmpFrame) { + const context = previousWindow.context; + + try { + foundReferrer = context.sourceUrl; + bestReferrer = foundReferrer; + + valuesFromAmp = true; + + if (currentWindow === win.top) { + reachedTop = true; + } + + if (context.canonicalUrl) { + bestCanonicalUrl = context.canonicalUrl; + } + } catch (e) { /* Do nothing */ } + } else { + logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); + + try { + const referrer = previousWindow.document.referrer; + + if (referrer) { + foundReferrer = referrer; + + if (currentWindow === win.top) { + reachedTop = true; + } + } + } catch (e) { /* Do nothing */ } + + if (!foundReferrer && ancestors && ancestors[level - 1]) { + foundReferrer = ancestors[level - 1]; + } + + if (foundReferrer && !valuesFromAmp) { + bestReferrer = foundReferrer; + } + } + } else { + if (currentLocation) { + foundReferrer = currentLocation; + bestReferrer = foundReferrer; + valuesFromAmp = false; + + if (currentWindow === win.top) { + reachedTop = true; + + const canonicalUrl = getCanonicalUrl(currentWindow.document); + + if (canonicalUrl) { + bestCanonicalUrl = canonicalUrl; + } + } + } + + if (currentWindow.context && currentWindow.context.sourceUrl) { + inAmpFrame = true; + } + } + + stack.push(foundReferrer); + level++; + } while (currentWindow !== win.top && level < maxNestedIframes); + + stack.reverse(); + + return { + referer: bestReferrer || null, + reachedTop, + isAmp: valuesFromAmp, + numIframes: level - 1, + stack, + canonicalUrl: bestCanonicalUrl || null + }; } return refererInfo; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 34de2be275c..60e60688c64 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,21 +4,23 @@ */ import events from './events.js'; -import { fireNativeTrackers, getAssetMessage } from './native.js'; -import { EVENTS } from './constants.json'; -import { logWarn, replaceAuctionPrice } from './utils.js'; +import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js'; +import constants from './constants.json'; +import { logWarn, replaceAuctionPrice, deepAccess, isGptPubadsDefined, isApnGetTagDefined } 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 includes from 'core-js-pure/features/array/includes.js'; +import { config } from './config.js'; -const BID_WON = EVENTS.BID_WON; +const BID_WON = constants.EVENTS.BID_WON; +const STALE_RENDER = constants.EVENTS.STALE_RENDER; export function listenMessagesFromCreative() { window.addEventListener('message', receiveMessage, false); } -function receiveMessage(ev) { +export function receiveMessage(ev) { var key = ev.message ? 'message' : 'data'; var data = {}; try { @@ -33,6 +35,14 @@ function receiveMessage(ev) { }); if (adObject && data.message === 'Prebid Request') { + 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; + } + } + _sendAdToCreative(adObject, ev); // save winning bids @@ -51,6 +61,13 @@ function receiveMessage(ev) { 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); } const trackerType = fireNativeTrackers(data, adObject); @@ -101,9 +118,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; 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 0d88a8ccea1..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } @@ -154,7 +159,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { */ const setDataInLocalStorage = function (key, value, done) { let cb = function (result) { - if (result && result.valid) { + if (result && result.valid && hasLocalStorage()) { window.localStorage.setItem(key, value); } } @@ -174,7 +179,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { */ const getDataFromLocalStorage = function (key, done) { let cb = function (result) { - if (result && result.valid) { + if (result && result.valid && hasLocalStorage()) { return window.localStorage.getItem(key); } return null; @@ -194,7 +199,7 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { */ const removeDataFromLocalStorage = function (key, done) { let cb = function (result) { - if (result && result.valid) { + if (result && result.valid && hasLocalStorage()) { window.localStorage.removeItem(key); } } diff --git a/src/targeting.js b/src/targeting.js index 45c098554a5..96692376d82 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -4,7 +4,9 @@ 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 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'); @@ -19,7 +21,7 @@ export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( ); // return unexpired bids -const isBidNotExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 + TTL_BUFFER) > timestamp(); +const isBidNotExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 - TTL_BUFFER) > timestamp(); // return bids whose status is not set. Winning bids can only have a status of `rendered`. const isUnusedBid = (bid) => bid && ((bid.status && !includes([CONSTANTS.BID_STATUS.RENDERED], bid.status)) || !bid.status); @@ -32,26 +34,31 @@ export let filters = { // If two bids are found for same adUnitCode, we will use the highest one to take part in auction // This can happen in case of concurrent auctions // If adUnitBidLimit is set above 0 return top N number of bids -export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, adUnitBidLimit = 0) { - const bids = []; - const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); - // bucket by adUnitcode - let buckets = groupBy(bidsReceived, 'adUnitCode'); - // filter top bid for each bucket by bidder - Object.keys(buckets).forEach(bucketKey => { - let bucketBids = []; - let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); - // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit > 0) { - bucketBids = dealPrioritization ? bucketBids(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); - bids.push(...bucketBids.slice(0, adUnitBidLimit)); - } else { - bids.push(...bucketBids); - } - }); - return bids; -} +export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + if (!hasModified) { + const bids = []; + const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); + // bucket by adUnitcode + let buckets = groupBy(bidsReceived, 'adUnitCode'); + // filter top bid for each bucket by bidder + Object.keys(buckets).forEach(bucketKey => { + let bucketBids = []; + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); + Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); + // if adUnitBidLimit is set, pass top N number bids + if (adUnitBidLimit > 0) { + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); + bids.push(...bucketBids.slice(0, adUnitBidLimit)); + } else { + bids.push(...bucketBids); + } + }); + + return bids; + } + + return bidsReceived; +}) /** * A descending sort function that will sort the list of objects based on the following two dimensions: @@ -76,11 +83,11 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, a */ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { return function(a, b) { - if (a.adUnitTargeting.hb_deal !== undefined && b.adUnitTargeting.hb_deal === undefined) { + if (a.adserverTargeting.hb_deal !== undefined && b.adserverTargeting.hb_deal === undefined) { return -1; } - if ((a.adUnitTargeting.hb_deal === undefined && b.adUnitTargeting.hb_deal !== undefined)) { + if ((a.adserverTargeting.hb_deal === undefined && b.adserverTargeting.hb_deal !== undefined)) { return 1; } @@ -89,7 +96,7 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { return b.cpm - a.cpm; } - return b.adUnitTargeting.hb_pb - a.adUnitTargeting.hb_pb; + return b.adserverTargeting.hb_pb - a.adserverTargeting.hb_pb; } } @@ -181,6 +188,48 @@ export function newTargeting(auctionManager) { return []; }; + /** + * Returns filtered ad server targeting for custom and allowed keys. + * @param {targetingArray} targeting + * @param {string[]} allowedKeys + * @return {targetingArray} filtered targeting + */ + function getAllowedTargetingKeyValues(targeting, allowedKeys) { + const defaultKeyring = Object.assign({}, CONSTANTS.TARGETING_KEYS, CONSTANTS.NATIVE_KEYS); + const defaultKeys = Object.keys(defaultKeyring); + const keyDispositions = {}; + logInfo(`allowTargetingKeys - allowed keys [ ${allowedKeys.map(k => defaultKeyring[k]).join(', ')} ]`); + targeting.map(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + const keys = keyring.filter(kvPair => { + const key = Object.keys(kvPair)[0]; + // check if key is in default keys, if not, it's custom, we won't remove it. + const isCustom = defaultKeys.filter(defaultKey => key.indexOf(defaultKeyring[defaultKey]) === 0).length === 0; + // check if key explicitly allowed, if not, we'll remove it. + const found = isCustom || find(allowedKeys, allowedKey => { + const allowedKeyName = defaultKeyring[allowedKey]; + // we're looking to see if the key exactly starts with one of our default keys. + // (which hopefully means it's not custom) + const found = key.indexOf(allowedKeyName) === 0; + return found; + }); + keyDispositions[key] = !found; + return found; + }); + adUnit[adUnitCode] = keys; + }); + const removedKeys = Object.keys(keyDispositions).filter(d => keyDispositions[d]); + logInfo(`allowTargetingKeys - removed keys [ ${removedKeys.join(', ')} ]`); + // remove any empty targeting objects, as they're unnecessary. + const filteredTargeting = targeting.filter(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + return keyring.length > 0; + }); + return filteredTargeting + } + /** * Returns all ad server targeting for all ad units. * @param {string=} adUnitCode @@ -193,7 +242,8 @@ export function newTargeting(auctionManager) { // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)); + .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)) + .concat(getAdUnitTargeting(adUnitCodes)); // store a reference of the targeting keys targeting.map(adUnitCode => { @@ -206,6 +256,12 @@ export function newTargeting(auctionManager) { }); }); + const defaultKeys = Object.keys(Object.assign({}, CONSTANTS.DEFAULT_TARGETING_KEYS, CONSTANTS.NATIVE_KEYS)); + const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys') || defaultKeys; + if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { + targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); + } + targeting = flattenTargeting(targeting); const auctionKeysThreshold = config.getConfig('targetingControls.auctionKeyMaxChars'); @@ -240,13 +296,13 @@ export function newTargeting(auctionManager) { let targetingMap = Object.keys(targetingCopy).map(adUnitCode => { return { adUnitCode, - adUnitTargeting: targetingCopy[adUnitCode] + adserverTargeting: targetingCopy[adUnitCode] }; }).sort(sortByDealAndPriceBucketOrCpm()); // iterate through the targeting based on above list and transform the keys into the query-equivalent and count characters return targetingMap.reduce(function (accMap, currMap, index, arr) { - let adUnitQueryString = convertKeysToQueryForm(currMap.adUnitTargeting); + let adUnitQueryString = convertKeysToQueryForm(currMap.adserverTargeting); // for the last adUnit - trim last encoded ampersand from the converted query string if ((index + 1) === arr.length) { @@ -281,7 +337,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"]}] * } * ] * ``` @@ -290,7 +346,7 @@ export function newTargeting(auctionManager) { * { * "div-gpt-ad-1460505748561-0": { * "hb_bidder": "appnexusAst", - * "hb_bidder_appnexusAs": "appnexusAst" + * "hb_bidder_appnexusAs": "appnexusAst,other" * } * } * ``` @@ -304,7 +360,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), {}) }; @@ -325,7 +381,10 @@ export function newTargeting(auctionManager) { Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)) .forEach(targetId => Object.keys(targetingConfig[targetId]).forEach(key => { - let valueArr = targetingConfig[targetId][key].split(','); + let valueArr = targetingConfig[targetId][key]; + if (typeof valueArr === 'string') { + valueArr = valueArr.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}`); @@ -538,13 +597,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) ) }; } @@ -559,6 +624,29 @@ export function newTargeting(auctionManager) { }); } + function getAdUnitTargeting(adUnitCodes) { + function getTargetingObj(adUnit) { + return deepAccess(adUnit, CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING); + } + + function getTargetingValues(adUnit) { + const aut = getTargetingObj(adUnit); + + return Object.keys(aut) + .map(function(key) { + if (utils.isStr(aut[key])) aut[key] = aut[key].split(',').map(s => s.trim()); + if (!utils.isArray(aut[key])) aut[key] = [ aut[key] ]; + return { [key]: aut[key] }; + }); + } + + return auctionManager.getAdUnits() + .filter(adUnit => includes(adUnitCodes, adUnit.code) && getTargetingObj(adUnit)) + .map(adUnit => { + return {[adUnit.code]: getTargetingValues(adUnit)} + }); + } + targeting.isApntagDefined = function() { if (window.apntag && utils.isFn(window.apntag.setKeywords)) { return true; diff --git a/src/userSync.js b/src/userSync.js index fceeb1d722d..f653880fa29 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -225,7 +225,7 @@ export function newUserSync(userSyncDependencies) { } return checkForFiltering[filterType](biddersToFilter, bidder); } - return false; + return !permittedPixels[type]; } /** diff --git a/src/utils.js b/src/utils.js index 88328ff10aa..03c76529ddf 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import { config } from './config.js'; import clone from 'just-clone'; -import deepequal from 'deep-equal'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -22,6 +21,7 @@ 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'); // this allows stubbing of utility functions that are used internally by other utility functions export const internal = { @@ -43,6 +43,14 @@ export const internal = { deepEqual }; +let prebidInternal = {} +/** + * Returns object that is used as internal prebid namespace + */ +export function getPrebidInternal() { + return prebidInternal; +} + var uniqueRef = {}; export let bind = function(a, b) { return b; }.bind(null, 1, uniqueRef)() === uniqueRef ? Function.prototype.bind @@ -256,20 +264,31 @@ export function logWarn() { if (debugTurnedOn() && consoleWarnExists) { console.warn.apply(console, decorateLog(arguments, 'WARNING:')); } + events.emit(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}); } function decorateLog(args, prefix) { args = [].slice.call(args); + let bidder = config.getCurrentBidder(); + prefix && args.unshift(prefix); - args.unshift('display: inline-block; color: #fff; background: #3b88c3; padding: 1px 4px; border-radius: 3px;'); - args.unshift('%cPrebid'); + if (bidder) { + args.unshift(label('#aaa')); + } + args.unshift(label('#3b88c3')); + args.unshift('%cPrebid' + (bidder ? `%c${bidder}` : '')); return args; + + function label(color) { + return `display: inline-block; color: #fff; background: ${color}; padding: 1px 4px; border-radius: 3px;` + } } export function hasConsoleLogger() { @@ -646,6 +665,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); @@ -717,10 +742,23 @@ export function replaceAuctionPrice(str, cpm) { return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); } +export function replaceClickThrough(str, clicktag) { + if (!str || !clicktag || typeof clicktag !== 'string') return; + return str.replace(/\${CLICKTHROUGH}/g, clicktag); +} + export function timestamp() { return new Date().getTime(); } +/** + * The returned value represents the time elapsed since the time origin. @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now + * @returns {number} + */ +export function getPerformanceNow() { + return (window.performance && window.performance.now && window.performance.now()) || 0; +} + /** * When the deviceAccess flag config option is false, no cookies should be read or set * @returns {boolean} @@ -1169,13 +1207,28 @@ export function buildUrl(obj) { } /** - * This function compares two objects for checking their equivalence. + * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 * @returns {boolean} */ export function deepEqual(obj1, obj2) { - return deepequal(obj1, obj2); + if (obj1 === obj2) return true; + else if ((typeof obj1 === 'object' && obj1 !== null) && (typeof obj2 === 'object' && obj2 !== null)) { + 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])) { + return false; + } + } else { + return false; + } + } + return true; + } else { + return false; + } } export function mergeDeep(target, ...sources) { @@ -1201,3 +1254,43 @@ export function mergeDeep(target, ...sources) { return mergeDeep(target, ...sources); } + +/** + * returns a hash of a string using a fast algorithm + * source: https://stackoverflow.com/a/52171480/845390 + * @param str + * @param seed (optional) + * @returns {string} + */ +export function cyrb53Hash(str, seed = 0) { + // IE doesn't support imul + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul#Polyfill + let imul = function(opA, opB) { + if (isFn(Math.imul)) { + return Math.imul(opA, opB); + } else { + opB |= 0; // ensure that opB is an integer. opA will automatically be coerced. + // floating points give us 53 bits of precision to work with plus 1 sign bit + // automatically handled for our convienence: + // 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001 + // 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + var result = (opA & 0x003fffff) * opB; + // 2. We can remove an integer coersion from the statement above because: + // 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001 + // 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/ + if (opA & 0xffc00000) result += (opA & 0xffc00000) * opB | 0; + return result | 0; + } + }; + + let h1 = 0xdeadbeef ^ seed; + let h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = imul(h1 ^ ch, 2654435761); + h2 = imul(h2 ^ ch, 1597334677); + } + h1 = imul(h1 ^ (h1 >>> 16), 2246822507) ^ imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = imul(h2 ^ (h2 >>> 16), 2246822507) ^ imul(h1 ^ (h1 >>> 13), 3266489909); + return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(); +} diff --git a/src/video.js b/src/video.js index befeb2ded39..20df7a92442 100644 --- a/src/video.js +++ b/src/video.js @@ -59,7 +59,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); + return !!(bid.renderer || bidRequest.renderer || videoMediaType.renderer); } return true; diff --git a/src/videoCache.js b/src/videoCache.js index 9f1fd7e4117..9e378d90574 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -71,6 +71,7 @@ function toStorageRequest(bid) { if (config.getConfig('cache.vasttrack')) { payload.bidder = bid.bidder; payload.bidid = bid.requestId; + payload.aid = bid.auctionId; // function has a thisArg set to bidderRequest for accessing the auctionStart if (utils.isPlainObject(this) && this.hasOwnProperty('auctionStart')) { payload.timestamp = this.auctionStart; diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 842bccd99b1..0ea3276dbaf 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,40 +1,41 @@ 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' }, - "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 2a0a7638fc4..908382f8daa 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -676,7 +676,13 @@ export function getAdUnits() { 'bidder': 'appnexus', 'params': { 'placementId': '543221', - 'test': 'me' + } + }, + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': 1234567, + 'adSlot': '1234567@728x90' } } ] diff --git a/test/fixtures/video/adUnit.json b/test/fixtures/video/adUnit.json index df55eb25d79..0773d7f3a62 100644 --- a/test/fixtures/video/adUnit.json +++ b/test/fixtures/video/adUnit.json @@ -1,7 +1,11 @@ { "code": "video1", - "sizes": [640,480], - "mediaType": "video", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480] + } + }, "bids": [ { "bidder": "appnexus", diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index b52ca5e9280..6e7f5756100 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -4,7 +4,7 @@ import * as adloader from 'src/adloader.js'; // this export is for adloader's tests against actual implementation export let loadExternalScript = adloader.loadExternalScript; -let stub = createStub(); +export let loadExternalScriptStub = createStub(); function createStub() { return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { @@ -18,6 +18,6 @@ function createStub() { } beforeEach(function() { - stub.restore(); - stub = createStub(); + loadExternalScriptStub.restore(); + loadExternalScriptStub = createStub(); }); diff --git a/test/mocks/fabrickId.json b/test/mocks/fabrickId.json new file mode 100644 index 00000000000..a8723ec88ec --- /dev/null +++ b/test/mocks/fabrickId.json @@ -0,0 +1,3 @@ +{ + "fabrickId": 1980 +} diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 4afa430f81e..a4acb6fc414 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -9,6 +9,8 @@ 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; const AnalyticsAdapter = require('src/AnalyticsAdapter').default; @@ -48,8 +50,10 @@ FEATURE: Analytics Adapters API events.emit(eventType, args); adapter.enableAnalytics(); - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {wat: 'wot'}, eventType: 'bidResponse'}); + // As now AUCTION_DEBUG is triggered for WARNINGS too, the BID_RESPONSE goes last in the array + const index = server.requests.length - 1; + let result = JSON.parse(server.requests[index].requestBody); + expect(result).to.deep.equal({eventType: 'bidResponse', args: {wat: 'wot'}}); }); describe(`WHEN an event occurs after enable analytics\n`, function () { @@ -83,6 +87,28 @@ 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' }; + + adapter.enableAnalytics(); + events.emit(eventType, args); + + let result = JSON.parse(server.requests[0].requestBody); + expect(result).to.deep.equal({args: {call: 'auctionDebug'}, eventType: 'auctionDebug'}); + }); + it('SHOULD call global when an addAdUnits event occurs', function () { const eventType = ADD_AD_UNITS; const args = { call: 'addAdUnits' }; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/adapters/adbutler_spec.js b/test/spec/adapters/adbutler_spec.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6f21eba7aaf..cb6c7aa87a3 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -43,10 +43,14 @@ describe('Publisher API', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponses); }); - it('should have function $$PREBID_GLOBAL$$.getBidResponses', function () { + it('should have function $$PREBID_GLOBAL$$.getNoBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getNoBids); }); + it('should have function $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getNoBidsForAdUnitCode); + }); + it('should have function $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode); }); @@ -78,5 +82,9 @@ describe('Publisher API', function () { it('should have function $$PREBID_GLOBAL$$.getAllWinningBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getAllWinningBids); }); + + it('should have function $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode); + }); }); }); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 35a29727614..69e34c4a07a 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -143,6 +143,9 @@ describe('auctionmanager.js', function () { adId: '1adId', source: 'client', mediaType: 'banner', + meta: { + advertiserDomains: ['adomain'] + } }; /* return the expected response for a given bid, filter by keys if given */ @@ -154,6 +157,7 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; + expected[ CONSTANTS.TARGETING_KEYS.ADOMAIN ] = bid.meta.advertiserDomains[0]; if (bid.mediaType === 'video') { expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; @@ -239,6 +243,12 @@ describe('auctionmanager.js', function () { return bidResponse.mediaType; } }, + { + key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } + } ] } @@ -309,6 +319,12 @@ describe('auctionmanager.js', function () { val: function (bidResponse) { return bidResponse.videoCacheKey; } + }, + { + key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } } ] @@ -740,6 +756,91 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, 'renderer.js'); }); + it('installs publisher-defined backup renderers on bids', function () { + let renderer = { + url: 'renderer.js', + 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); + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: BIDDER_CODE, + mediaType: 'video-outstream', + } + ); + spec.interpretResponse.returns(bids1); + auction.callBids(); + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, 'renderer.js'); + }); + + it('installs publisher-defined renderers for a media type', function () { + const renderer = { + url: 'videoRenderer.js', + render: (bid) => bid + }; + let myBid = mockBid(); + let bidRequest = mockBidRequest(myBid); + + bidRequest.bids[0] = { + ...bidRequest.bids[0], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + renderer + } + } + }; + makeRequestsStub.returns([bidRequest]); + + myBid.mediaType = 'video'; + spec.interpretResponse.returns(myBid); + auction.callBids(); + + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, renderer.url); + }); + + it('installs bidder-defined renderer when onlyBackup is true in mediaTypes.video options ', function () { + const renderer = { + url: 'videoRenderer.js', + backupOnly: true, + render: (bid) => bid + }; + let myBid = mockBid(); + let bidRequest = mockBidRequest(myBid); + + bidRequest.bids[0] = { + ...bidRequest.bids[0], + mediaTypes: { + video: { + context: 'outstream', + renderer + } + } + }; + makeRequestsStub.returns([bidRequest]); + + myBid.mediaType = 'video'; + myBid.renderer = { + url: 'renderer.js', + render: sinon.spy() + }; + spec.interpretResponse.returns(myBid); + auction.callBids(); + + const addedBid = auction.getBidsReceived().pop(); + assert.strictEqual(addedBid.renderer.url, myBid.renderer.url); + }); + it('bid for a regular unit and a video unit', function() { let renderer = { url: 'renderer.js', @@ -1229,4 +1330,119 @@ describe('auctionmanager.js', function () { assert.equal(doneSpy.callCount, 1); }) }); + + 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'; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + doneSpy = sinon.spy(); + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [ secondaryBidder ] + } + }) + }); + + afterEach(() => { + doneSpy.resetHistory(); + config.resetConfig(); + clock.restore(); + }); + + 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 })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + // required bidder responds immeaditely to auction + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + assert.equal(doneSpy.callCount, 0); + + // auction waits for second required bidder to respond + clock.tick(100); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + + // auction done is reported and does not wait for secondaryBidder request + assert.equal(doneSpy.callCount, 1); + + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + }); + + it('should wait for all bidders if they are all secondary', function () { + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [requiredBidder, requiredBidder1, secondaryBidder] + } + }) + let bids1 = [mockBid({ bidderCode: requiredBidder })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0) + + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + assert.equal(doneSpy.callCount, 1); + }); + + it('should allow secondaryBidders to respond in auction before is is done', function () { + let bids1 = [mockBid({ bidderCode: requiredBidder })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + // secondaryBidder is first to respond + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + // first required bidder takes longest to respond, auction isn't marked as done until this occurs + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + assert.equal(doneSpy.callCount, 1); + }); + }); }); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index be5b4bbb78b..6cc1bd557d5 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,9 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let readConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +18,9 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + readConfig = config.readConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -33,6 +39,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'); @@ -57,6 +124,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; @@ -195,6 +273,12 @@ describe('config API', function () { expect(getConfig('deviceAccess')).to.be.equal(true); }); + it('sets maxNestedIframes', function () { + expect(getConfig('maxNestedIframes')).to.be.equal(10); + setConfig({ maxNestedIframes: 2 }); + expect(getConfig('maxNestedIframes')).to.be.equal(2); + }); + it('should log error for invalid priceGranularity', function () { setConfig({ priceGranularity: '' }); const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; @@ -211,4 +295,54 @@ describe('config API', function () { setConfig({ bidderSequence: 'random' }); expect(logWarnSpy.called).to.equal(false); }); + + it('sets auctionOptions secondaryBidders', function () { + const auctionOptionsConfig = { + 'secondaryBidders': ['rubicon', 'appnexus'] + } + setConfig({ auctionOptions: auctionOptionsConfig }); + 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); + const warning = 'Auction Options must be an object'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); + + it('should log warning for invalid auctionOptions bidder values', function () { + setConfig({ auctionOptions: { + 'secondaryBidders': 'appnexus, rubicon', + }}); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options secondaryBidders must be of type Array'; + 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 + }}); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options given an incorrect param: testing'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); }); 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 a0ce04402f7..1d1a7512153 100644 --- a/test/spec/integration/faker/googletag.js +++ b/test/spec/integration/faker/googletag.js @@ -43,18 +43,57 @@ export function makeSlot() { return slot; } -window.googletag = { - _slots: [], - pubads: function () { - var self = this; - return { - getSlots: function () { - return self._slots; - }, - - setSlots: function (slots) { - self._slots = slots; - } - }; - } -}; +export function emitEvent(eventName, params) { + (window.googletag._callbackMap[eventName] || []).forEach(eventCb => eventCb({...params, eventName})); +} + +export function enable() { + window.googletag = { + _slots: [], + _callbackMap: {}, + pubads: function () { + var self = this; + return { + getSlots: function () { + return self._slots; + }, + + setSlots: function (slots) { + self._slots = slots; + }, + + setTargeting: function(key, arrayOfValues) { + self._targeting[key] = Array.isArray(arrayOfValues) ? arrayOfValues : [arrayOfValues]; + }, + + getTargeting: function(key) { + return self._targeting[key] || []; + }, + + getTargetingKeys: function() { + return Object.getOwnPropertyNames(self._targeting); + }, + + clearTargeting: function() { + self._targeting = {}; + }, + + addEventListener: function (eventName, cb) { + self._callbackMap[eventName] = self._callbackMap[eventName] || []; + self._callbackMap[eventName].push(cb); + } + }; + } + }; +} + +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/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 3721cef18d9..b5443cdd5c2 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -5,10 +5,18 @@ import { config } from 'src/config.js'; import { spec } from 'modules/33acrossBidAdapter.js'; +function validateBuiltServerRequest(builtReq, expectedReq) { + expect(builtReq.url).to.equal(expectedReq.url); + expect(builtReq.options).to.deep.equal(expectedReq.options); + expect(JSON.parse(builtReq.data)).to.deep.equal( + JSON.parse(expectedReq.data) + ) +} + describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; - const SITE_ID = 'pub1234'; - const PRODUCT_ID = 'product1'; + const SITE_ID = 'sample33xGUID123456789'; + const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; @@ -17,7 +25,29 @@ describe('33acrossBidAdapter:', function () { function TtxRequestBuilder() { const ttxRequest = { - imp: [{ + imp: [{}], + site: { + id: SITE_ID + }, + id: 'b1', + regs: { + ext: { + gdpr: 0 + } + }, + ext: { + ttx: { + prebidStartedAt: 1, + caller: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] + } + } + }; + + this.withBanner = () => { + Object.assign(ttxRequest.imp[0], { banner: { format: [ { @@ -36,46 +66,32 @@ describe('33acrossBidAdapter:', function () { } } } - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - }], - site: { - id: SITE_ID - }, - id: 'b1', - user: { - ext: { - consent: undefined - } - }, - regs: { - ext: { - gdpr: 0, - us_privacy: null } - }, - ext: { - ttx: { - prebidStartedAt: 1, - caller: [{ - 'name': 'prebidjs', - 'version': '$prebid.version$' - }] - } - } + }); + + return this; }; - this.withSizes = sizes => { + this.withBannerSizes = this.withSizes = sizes => { Object.assign(ttxRequest.imp[0].banner, { format: sizes }); return this; }; - this.withViewability = viewability => { - Object.assign(ttxRequest.imp[0].banner, { + this.withVideo = (params = {}) => { + Object.assign(ttxRequest.imp[0], { + video: { + w: 300, + h: 250, + placement: 2, + ...params + } + }); + + return this; + }; + + this.withViewability = (viewability, format = 'banner') => { + Object.assign(ttxRequest.imp[0][format], { ext: { ttx: { viewability } } @@ -83,6 +99,18 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withProduct = (prod = PRODUCT_ID) => { + Object.assign(ttxRequest.imp[0], { + ext: { + ttx: { + prod + } + } + }); + + return this; + }; + this.withGdprConsent = (consent, gdpr) => { Object.assign(ttxRequest, { user: { @@ -140,21 +168,46 @@ describe('33acrossBidAdapter:', function () { return this; }; - this.withFormatFloors = floors => { - const format = ttxRequest.imp[0].banner.format.map((fm, i) => { - return Object.assign(fm, { - ext: { - ttx: { - bidfloors: [ floors[i] ] + this.withFloors = this.withFormatFloors = (mediaType, floors) => { + switch (mediaType) { + case 'banner': + const format = ttxRequest.imp[0].banner.format.map((fm, i) => { + return Object.assign(fm, { + ext: { + ttx: { + bidfloors: [ floors[i] ] + } + } + }) + }); + + ttxRequest.imp[0].banner.format = format; + break; + case 'video': + Object.assign(ttxRequest.imp[0].video, { + ext: { + ttx: { + bidfloors: floors + } } + }); + break; + } + + return this; + }; + + this.withUserIds = (eids) => { + Object.assign(ttxRequest, { + user: { + ext: { + eids } - }) + } }); - ttxRequest.imp[0].banner.format = format; - return this; - }; + } this.build = () => ttxRequest; } @@ -162,7 +215,7 @@ describe('33acrossBidAdapter:', function () { function ServerRequestBuilder() { const serverRequest = { 'method': 'POST', - 'url': END_POINT, + 'url': `${END_POINT}?guid=${SITE_ID}`, 'data': null, 'options': { 'contentType': 'text/plain', @@ -188,6 +241,59 @@ describe('33acrossBidAdapter:', function () { this.build = () => serverRequest; } + function BidRequestsBuilder() { + const bidRequests = [ + { + bidId: 'b1', + bidder: '33across', + bidderRequestId: 'b1a', + params: { + siteId: SITE_ID, + productId: PRODUCT_ID + }, + adUnitCode: 'div-id', + auctionId: 'r1', + mediaTypes: {}, + transactionId: 't1' + } + ]; + + this.withBanner = () => { + bidRequests[0].mediaTypes.banner = { + sizes: [ + [300, 250], + [728, 90] + ] + }; + + return this; + }; + + this.withProduct = (prod) => { + bidRequests[0].params.productId = prod; + + return this; + }; + + this.withVideo = (params) => { + bidRequests[0].mediaTypes.video = { + playerSize: [[300, 250]], + context: 'outstream', + ...params + }; + + return this; + } + + this.withUserIds = (eids) => { + bidRequests[0].userIdAsEids = eids; + + return this; + }; + + this.build = () => bidRequests; + } + beforeEach(function() { element = { x: 0, @@ -217,24 +323,11 @@ describe('33acrossBidAdapter:', function () { innerHeight: 600 }; - bidRequests = [ - { - bidId: 'b1', - bidder: '33across', - bidderRequestId: 'b1a', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - }, - adUnitCode: 'div-id', - auctionId: 'r1', - sizes: [ - [300, 250], - [728, 90] - ], - transactionId: 't1' - } - ]; + bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .build() + ); sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); @@ -248,78 +341,246 @@ describe('33acrossBidAdapter:', function () { }); describe('isBidRequestValid:', function() { - it('returns true when valid bid request is sent', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } - }; + context('basic validation', function() { + it('returns true for valid guid values', function() { + // NOTE: We ignore whitespace at the start and end since + // in our experience these are common typos + const validGUIDs = [ + `${SITE_ID}`, + `${SITE_ID} `, + ` ${SITE_ID}`, + ` ${SITE_ID} ` + ]; - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); + validGUIDs.forEach((siteId) => { + const bid = { + bidder: '33across', + params: { + siteId + } + }; - it('returns true when valid test bid request is sent', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID, - test: 1 - } - }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); + it('returns false for invalid guid values', function() { + const invalidGUIDs = [ + undefined, + 'siab' + ]; - it('returns false when bidder not set to "33across"', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } - }; + invalidGUIDs.forEach((siteId) => { + const bid = { + bidder: '33across', + params: { + siteId + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); }); - it('returns false when params not set', function() { - const invalidBid = { - bidder: 'foo' - }; + context('banner validation', function() { + it('returns true when banner mediaType does not exist', function() { + const bid = { + bidder: '33across', + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); - it('returns false when site ID is not set in params', function() { - const invalidBid = { - bidder: 'foo', - params: { - productId: PRODUCT_ID - } - }; + it('returns true when banner sizes are defined', function() { + const bid = { + bidder: '33across', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); - it('returns false when product ID not set in params', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID - } - }; + it('returns false when banner sizes are invalid', function() { + const invalidSizes = [ + undefined, + '16:9', + 300, + 'foo' + ]; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + invalidSizes.forEach((sizes) => { + const bid = { + bidder: '33across', + mediaTypes: { + banner: { + sizes + } + }, + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); }); + + context('video validation', function() { + beforeEach(function() { + // Basic Valid BidRequest + this.bid = { + bidder: '33across', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'outstream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + siteId: `${SITE_ID}` + } + }; + }); + + it('returns true when video mediaType does not exist', function() { + const bid = { + bidder: '33across', + params: { + siteId: `${SITE_ID}` + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns true when valid video mediaType is defined', function() { + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + + it('returns false when video context is not defined', function() { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function() { + const invalidSizes = [ + undefined, + '16:9', + 300, + 'foo' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function() { + const invalidMimes = [ + undefined, + 'foo', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function() { + const invalidMimes = [ + undefined, + 'foo', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video placement is invalid', function() { + const invalidPlacement = [ + [], + '1', + {}, + 'foo' + ]; + + invalidPlacement.forEach((placement) => { + this.bid.mediaTypes.video.placement = placement; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video startdelay is invalid for instream context', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream', protocols: [1, 2], mimes: ['foo', 'bar']}) + .build() + ); + + const invalidStartdelay = [ + [], + '1', + {}, + 'foo' + ]; + + invalidStartdelay.forEach((startdelay) => { + bidRequests[0].mediaTypes.video.startdelay = startdelay; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.false; + }); + }); + + it('returns true when video startdelay is invalid for outstream context', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', protocols: [1, 2], mimes: ['foo', 'bar']}) + .build() + ); + + const invalidStartdelay = [ + [], + '1', + {}, + 'foo' + ]; + + invalidStartdelay.forEach((startdelay) => { + bidRequests[0].mediaTypes.video.startdelay = startdelay; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + }); + }); + }) }); describe('buildRequests:', function() { context('when element is fully in view', function() { it('returns 100', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 100}) .build(); const serverRequest = new ServerRequestBuilder() @@ -328,13 +589,16 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 600, height: 400 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when element is out of view', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() @@ -343,13 +607,16 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when element is partially in view', function() { it('returns percentage', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 75}) .build(); const serverRequest = new ServerRequestBuilder() @@ -358,13 +625,16 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 800, height: 800 }); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when width or height of the element is zero', function() { it('try to use alternative values', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSizes([{ w: 800, h: 2400 }]) .withViewability({amount: 25}) .build(); @@ -373,15 +643,18 @@ describe('33acrossBidAdapter:', function () { .build(); Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].sizes = [[800, 2400]]; + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when nested iframes', function() { it('returns \'nm\'', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: spec.NON_MEASURABLE}) .build(); const serverRequest = new ServerRequestBuilder() @@ -395,13 +668,16 @@ describe('33acrossBidAdapter:', function () { sandbox.stub(utils, 'getWindowTop').returns({}); sandbox.stub(utils, 'getWindowSelf').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when tab is inactive', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() @@ -414,7 +690,8 @@ describe('33acrossBidAdapter:', function () { win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); - expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -432,14 +709,16 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with gdpr consent data', function() { @@ -450,15 +729,17 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -471,13 +752,15 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with default gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default gdpr consent data', function() { @@ -488,14 +771,16 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -510,14 +795,16 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with us_privacy consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with us_privacy consent data', function() { @@ -528,15 +815,17 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -549,13 +838,15 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with default us_privacy data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('returns corresponding test server requests with default us_privacy consent data', function() { @@ -566,14 +857,16 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .withUrl('https://foo.com/hb/') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -586,15 +879,17 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withPageUrl('http://foo.com/bar') .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -605,14 +900,16 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); @@ -656,15 +953,17 @@ describe('33acrossBidAdapter:', function () { bidRequests[0].schain = schain; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSchain(schain) .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); }); @@ -672,43 +971,49 @@ describe('33acrossBidAdapter:', function () { context('when there no schain object is passed', function() { it('does not set source field', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); - context('when price floor module is not enabled in bidRequest', function() { + context('when price floor module is not enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); }); - context('when price floor module is enabled in bidRequest', function() { + context('when price floor module is enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest if there is no floor', function() { bidRequests[0].getFloor = () => ({}); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + validateBuiltServerRequest(builtServerRequest, serverRequest); }); it('sets bidfloors in ttxRequest if there is a floor', function() { @@ -723,15 +1028,352 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() - .withFormatFloors([ 1.0, 0.10 ]) + .withBanner() + .withProduct() + .withFormatFloors('banner', [ 1.0, 0.10 ]) + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when mediaType has video only and context is instream', function() { + it('builds instream request with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') .build(); + ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.startdelay = 0; + const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + it('builds instream request with params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream', startdelay: -2}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({startdelay: -2, placement: 1}) + .withProduct('instream') + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when mediaType has video only and context is outstream', function() { + it('builds siab request with video only with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('siab') + .build(); + + ttxRequest.imp[0].video.placement = 2; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + it('builds siab request with video params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({placement: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when mediaType has banner only', function() { + it('builds default siab request', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + it('builds default inview request when product is set as such', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withProduct('inview') + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct('inview') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when mediaType has banner and video', function() { + it('builds siab request with banner and outstream video', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withVideo({context: 'outstream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + it('builds siab request with banner and outstream video even when context is instream', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withVideo({context: 'instream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + ttxRequest.imp[0].video.placement = 2; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when price floor module is enabled for video in bidRequest', function() { + it('does not set any bidfloors in video if there is no floor', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + bidRequests[0].getFloor = () => ({}); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + it('sets bidfloors in video if there is a floor', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + bidRequests[0].getFloor = ({size, currency, mediaType}) => { + const floor = (mediaType === 'video') ? 1.0 : 0.10 + return ( + { + floor, + currency: 'USD' + } + ); + }; + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct() + .withFloors('video', [ 1.0 ]) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user ID data exists as userIdAsEids Array in bidRequest', function() { + it('passes userIds in eids field in ORTB request', function() { + const eids = [ + { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + ]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); - expect(builtServerRequests).to.deep.equal([serverRequest]); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + it('does not validate eids ORTB', function() { + const eids = [1, 2, 3]; + + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withUserIds(eids) + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); + + context('when user IDs do not exist under the userIdAsEids field in bidRequest as a non-empty Array', function() { + it('does not pass user IDs in the bidRequest ORTB', function() { + const eidsScenarios = [ + 'foo', + [], + {foo: 1} + ]; + + eidsScenarios.forEach((eids) => { + const bidRequests = ( + new BidRequestsBuilder() + .withUserIds(eids) + .build() + ); + bidRequests.userId = { + 'vendorx': { + 'source': 'x-device-vendor-x.com', + 'uids': [ + { + 'id': 'yyy', + 'atype': 1 + }, + { + 'id': 'zzz', + 'atype': 1 + }, + { + 'id': 'DB700403-9A24-4A4B-A8D5-8A0B4BE777D2', + 'atype': 2 + } + ], + 'ext': { + 'foo': 'bar' + } + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withProduct() + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); }); }); }); @@ -741,6 +1383,8 @@ describe('33acrossBidAdapter:', function () { beforeEach(function() { ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSite({ id: SITE_ID, page: 'https://test-url.com' @@ -757,7 +1401,7 @@ describe('33acrossBidAdapter:', function () { }); context('when exactly one bid is returned', function() { - it('interprets and returns the single bid response', function() { + it('interprets and returns the single banner bid response', function() { const serverResponse = { cur: 'USD', ext: {}, @@ -770,7 +1414,8 @@ describe('33acrossBidAdapter:', function () { crid: 1, h: 250, w: 300, - price: 0.0938 + price: 0.0938, + adomain: ['advertiserdomain.com'] }] } ] @@ -784,12 +1429,103 @@ describe('33acrossBidAdapter:', function () { ad: '

I am an ad

', ttl: 60, creativeId: 1, + mediaType: 'banner', currency: 'USD', - netRevenue: true + netRevenue: true, + meta: { + advertiserDomains: ['advertiserdomain.com'] + } }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); + + it('interprets and returns the single video bid response', function() { + const videoBid = ''; + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [ + { + bid: [{ + id: '1', + adm: videoBid, + ext: { + ttx: { + mediaType: 'video', + vastType: 'xml' + } + }, + crid: 1, + h: 250, + w: 300, + price: 0.0938, + adomain: ['advertiserdomain.com'] + }] + } + ] + }; + const bidResponse = { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: videoBid, + ttl: 60, + creativeId: 1, + mediaType: 'video', + currency: 'USD', + netRevenue: true, + vastXml: videoBid, + meta: { + advertiserDomains: ['advertiserdomain.com'] + } + }; + + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + }); + + context('when the list of advertiser domains for block list checking is empty', function() { + it('doesn\'t include the empty list in the interpreted response', function() { + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [ + { + bid: [{ + id: '1', + adm: '

I am an ad

', + crid: 1, + h: 250, + w: 300, + price: 0.0938, + adomain: [] // Empty list + }] + } + ] + }; + + // Bid response below doesn't contain meta.advertiserDomains + const bidResponse = { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }; + + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + }); + }); }); context('when no bids are returned', function() { @@ -852,6 +1588,7 @@ describe('33acrossBidAdapter:', function () { ad: '

I am an ad

', ttl: 60, creativeId: 1, + mediaType: 'banner', currency: 'USD', netRevenue: true }; @@ -886,9 +1623,13 @@ describe('33acrossBidAdapter:', function () { }, adUnitCode: 'div-id', auctionId: 'r1', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, transactionId: 't1' }, { @@ -901,9 +1642,13 @@ describe('33acrossBidAdapter:', function () { }, adUnitCode: 'div-id', auctionId: 'r1', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, transactionId: 't2' } ]; 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 new file mode 100644 index 00000000000..a9ead9fd021 --- /dev/null +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -0,0 +1,235 @@ +import { expect } from 'chai'; +import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('a4gAdapterTests', function () { + describe('bidRequestValidity', function () { + it('bidRequest with zoneId and deliveryUrl params', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304, + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(true); + }); + + it('bidRequest with only zoneId', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304 + } + })).to.equal(true); + }); + + it('bidRequest with only deliveryUrl', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(false); + }); + }); + + describe('bidRequest', function () { + const DEFAULT_OPTION = { + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const bidRequests = [{ + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }, { + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59354, + 'deliveryUrl': '//dev01.ad4game.com/v1/bid' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + it('bidRequest method', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.method).to.equal('GET'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.url).to.match(new RegExp(`${bidRequests[1].params.deliveryUrl}`)); + }); + + it('bidRequest data', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.data).to.exist; + }); + + it('bidRequest zoneIds', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.data.zoneId).to.equal('59304;59354'); + }); + + it('bidRequest gdpr consent', function () { + const consentString = 'consentString'; + const bidderRequest = { + bidderCode: 'a4g', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr.applies).to.exist.and.to.be.true; + expect(request.data.gdpr.consent).to.exist.and.to.equal(consentString); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = [{ + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + const bidResponse = { + body: [{ + 'id': '51ef8751f9aead', + 'ad': 'test ad', + 'width': 320, + 'height': 250, + 'cpm': 5.2, + 'crid': '111' + }], + headers: {} + }; + + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true, + meta: { + advertiserDomains: [] + } + + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true, + meta: { + advertiserDomains: [] + } + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + + it('required keys', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let requiredKeys = [ + 'requestId', + 'creativeId', + 'adId', + 'cpm', + 'width', + 'height', + 'currency', + 'netRevenue', + 'ttl', + 'ad', + 'meta' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(requiredKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + 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/ablidaBidAdapter_spec.js b/test/spec/modules/ablidaBidAdapter_spec.js index 0238c14fe5c..73109d8cf16 100644 --- a/test/spec/modules/ablidaBidAdapter_spec.js +++ b/test/spec/modules/ablidaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {assert, expect} from 'chai'; import {spec} from 'modules/ablidaBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -8,17 +9,23 @@ describe('ablidaBidAdapter', function () { const adapter = newBidder(spec); describe('isBidRequestValid', function () { let bid = { + adUnitCode: 'adunit-code', + auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0', + bidRequestsCount: 1, bidder: 'ablida', + bidderRequestId: '14d2939272a26a', + bidderRequestsCount: 1, + bidderWinsCount: 0, + bidId: '1234asdf1234', + mediaTypes: {banner: {sizes: [[300, 250]]}}, params: { placementId: 123 }, - adUnitCode: 'adunit-code', sizes: [ [300, 250] ], - bidId: '1234asdf1234', - bidderRequestId: '1234asdf1234asdf', - auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0' + src: 'client', + transactionId: '4781e6ac-93c4-42ba-86fe-ab5f278863cf' }; it('should return true where required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); @@ -27,22 +34,29 @@ describe('ablidaBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [ { + adUnitCode: 'adunit-code', + auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0', + bidId: '23beaa6af6cdde', + bidRequestsCount: 1, bidder: 'ablida', + bidderRequestId: '14d2939272a26a', + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: {banner: {sizes: [[300, 250]]}}, params: { placementId: 123 }, sizes: [ [300, 250] ], - adUnitCode: 'adunit-code', - bidId: '23beaa6af6cdde', - bidderRequestId: '14d2939272a26a', - auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0', + src: 'client', + transactionId: '4781e6ac-93c4-42ba-86fe-ab5f278863cf' } ]; let bidderRequests = { refererInfo: { + canonicalUrl: '', numIframes: 0, reachedTop: true, referer: 'http://example.com', @@ -61,42 +75,53 @@ describe('ablidaBidAdapter', function () { method: 'POST', url: ENDPOINT_URL, data: { + adapterVersion: 5, + bidId: '2b8c4de0116e54', + categories: undefined, + device: 'desktop', + gdprConsent: undefined, + jaySupported: true, + mediaTypes: {banner: {sizes: [[300, 250]]}}, placementId: 'testPlacementId', width: 300, height: 200, - bidId: '2b8c4de0116e54', - jaySupported: true, - device: 'desktop', - referer: 'www.example.com', - adapterVersion: 2 + referer: 'www.example.com' } }; let serverResponse = { body: [{ - requestId: '2b8c4de0116e54', + ad: '', cpm: 1.00, - width: 300, - height: 250, creativeId: '2b8c4de0116e54', currency: 'EUR', + height: 250, + mediaType: 'banner', + meta: {}, netRevenue: true, + nurl: 'https://example.com/some-tracker', + originalCpm: '0.10', + originalCurrency: 'EUR', + requestId: '2b8c4de0116e54', ttl: 3000, - ad: '', - nurl: 'https://example.com/some-tracker' + width: 300 }] }; it('should get the correct bid response', function () { let expectedResponse = [{ - requestId: '2b8c4de0116e54', + ad: '', cpm: 1.00, - width: 300, - height: 250, creativeId: '2b8c4de0116e54', currency: 'EUR', + height: 250, + mediaType: 'banner', + meta: {}, netRevenue: true, + nurl: 'https://example.com/some-tracker', + originalCpm: '0.10', + originalCurrency: 'EUR', + requestId: '2b8c4de0116e54', ttl: 3000, - ad: '', - nurl: 'https://example.com/some-tracker' + width: 300 }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); @@ -104,16 +129,23 @@ describe('ablidaBidAdapter', function () { }); describe('onBidWon', function() { - it('Should not ajax call if bid does not contain nurl', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function() { const result = spec.onBidWon({}); - expect(result).to.equal(false) + expect(utils.triggerPixel.callCount).to.equal(0) }) - it('Should ajax call if bid nurl', function() { + it('Should trigger pixel if bid nurl', function() { const result = spec.onBidWon({ nurl: 'https://example.com/some-tracker' }); - expect(result).to.equal(true) + expect(utils.triggerPixel.callCount).to.equal(1) }) }) }); diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js new file mode 100644 index 00000000000..db536ca14e2 --- /dev/null +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -0,0 +1,335 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adWMGBidAdapter.js'; +import { config } from 'src/config.js'; + +describe('adWMGBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid; + beforeEach(function() { + bid = { + bidder: 'adWMG', + params: { + publisherId: '5cebea3c9eea646c7b623d5e' + }, + 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 "adWMG"', function() { + bid.bidder = 'bidder'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when \'publisherId\' param are not set', function() { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('parseUserAgent', function() { + let ua_desktop, ua_mobile, ua_tv, ua_tablet; + beforeEach(function() { + ua_desktop = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'; + ua_tv = 'Mozilla/5.0 (Linux; NetCast; U) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.31 SmartTV/7.0'; + ua_mobile = 'Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 Chrome/59.0.3071.125 Mobile Safari/537.36'; + ua_tablet = 'Mozilla/5.0 (iPad; CPU OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53'; + }); + + it('should return correct device type: desktop', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.devicetype).to.equal(2); + }); + + it('should return correct device type: TV', function() { + let userDeviceInfo = spec.parseUserAgent(ua_tv); + expect(userDeviceInfo.devicetype).to.equal(3); + }); + + it('should return correct device type: mobile', function() { + let userDeviceInfo = spec.parseUserAgent(ua_mobile); + expect(userDeviceInfo.devicetype).to.equal(4); + }); + + it('should return correct device type: tablet', function() { + let userDeviceInfo = spec.parseUserAgent(ua_tablet); + expect(userDeviceInfo.devicetype).to.equal(5); + }); + + it('should return correct OS name', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.os).to.equal('Windows'); + }); + + it('should return correct OS version', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.osv).to.equal('10.0'); + }); + }); + + describe('buildRequests', function () { + let bidRequests; + beforeEach(function() { + bidRequests = [ + { + bidder: 'adWMG', + adUnitCode: 'adwmg-test-ad', + auctionId: 'test-auction-id', + bidId: 'test-bid-id', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid123', + transactionId: 'transaction-id-123', + sizes: [[300, 250]], + requestId: 'requestid123', + params: { + floorPrice: 100, + currency: 'USD' + }, + mediaTypes: { + banner: { + size: [[300, 250]] + } + }, + userId: { + pubcid: 'pubc-id-123' + } + }, { + bidder: 'adWMG', + adUnitCode: 'adwmg-test-ad-2', + auctionId: 'test-auction-id-2', + bidId: 'test-bid-id-2', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid456', + transactionId: 'transaction-id-456', + sizes: [[320, 50]], + requestId: 'requestid456', + params: { + floorPrice: 100, + currency: 'USD' + }, + mediaTypes: { + banner: { + size: [[320, 50]] + } + }, + userId: { + pubcid: 'pubc-id-456' + } + } + ]; + }); + + let bidderRequest = { + refererInfo: { + referer: 'https://test.com' + }, + gdprConsent: { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + } + }; + + 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(JSON.parse(requests[0].data).sizes).to.be.an('undefined'); + expect(JSON.parse(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(JSON.parse(requests[0].data).userId).to.be.an('undefined'); + expect(JSON.parse(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://hb.adwmg.com/hb'); + expect(requests[1].url).to.equal('https://hb.adwmg.com/hb'); + }); + + it('should contain GDPR consent data if GDPR set', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).gdpr.applies).to.be.true; + expect(JSON.parse(requests[0].data).gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(JSON.parse(requests[1].data).gdpr.applies).to.be.true; + expect(JSON.parse(requests[1].data).gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + }) + + it('should not contain GDPR consent data if GDPR not set', function() { + delete bidderRequest.gdprConsent; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).gdpr).to.be.an('undefined'); + expect(JSON.parse(requests[1].data).gdpr).to.be.an('undefined'); + }) + + it('should set debug mode in requests if enabled', function() { + sinon.stub(config, 'getConfig').withArgs('debug').returns(true); + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).debug).to.be.true; + expect(JSON.parse(requests[1].data).debug).to.be.true; + config.getConfig.restore(); + }) + }); + + 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': 'USD', + 'adomain': ['testdomain.com'] + } + }; + }); + + 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', 'meta', '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('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', () => { + 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 only 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).includes('https://hb.adwmg.com/cphb.html?'); + }); + + it('should register iframe sync when iframe and image are enabled', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).includes('https://hb.adwmg.com/cphb.html?'); + }); + + it('should send GDPR consent if enabled', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + expect(syncs[0].url).includes('gdpr=1'); + expect(syncs[0].url).includes(`gdpr_consent=${gdprConsent.consentString}`); + }); + + it('should not add GDPR consent params twice', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const gdprConsent2 = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_7_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent2); + expect(syncs[0].url.match(/gdpr/g).length).to.equal(2); // gdpr + gdpr_consent + expect(syncs[0].url.match(/gdpr_consent/g).length).to.equal(1); + }); + + it('should delete \'&\' symbol at the end of usersync URL', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + expect(syncs[0].url.slice(-1)).to.not.equal('&'); + }); + }); +}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 97265121ce0..ed3d26c1f4d 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,678 +1,811 @@ import { expect } from 'chai'; -import { adagioScriptFromLocalStorageCb, _features, spec } from 'modules/adagioBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from 'src/utils.js'; +import { + _features, + internal as adagio, + adagioScriptFromLocalStorageCb, + getAdagioScript, + storage, + setExtraParam, + spec, + ENDPOINT, + VERSION, + 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 = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'adagio' + }, + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code' + }, + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'adagio', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; -describe('adagioAdapter', () => { +describe('Adagio bid adapter', () => { + let adagioMock; let utilsMock; - const adapter = newBidder(spec); - const ENDPOINT = 'https://mp.4dex.io/prebid'; - const VERSION = '2.2.2'; + let sandbox; + + const fixtures = { + getElementById(width, height, x, y) { + const obj = { + x: x || 800, + y: y || 300, + width: width || 300, + height: height || 250, + }; + + return { + ...obj, + getBoundingClientRect: () => { + return { + width: obj.width, + height: obj.height, + left: obj.x, + top: obj.y, + right: obj.x + obj.width, + bottom: obj.y + obj.height + }; + } + }; + } + }; + + // safeFrame implementation + const $sf = { + ext: { + geom: function() {} + } + }; + + beforeEach(() => { + window.ADAGIO = {}; + window.ADAGIO.adUnits = {}; + window.ADAGIO.pbjsAdUnits = []; + window.ADAGIO.queue = []; + window.ADAGIO.versions = {}; + window.ADAGIO.versions.adagioBidderAdapter = VERSION; + window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; + + GlobalExchange.clearFeatures(); + GlobalExchange.clearExchangeData(); - beforeEach(function() { - localStorage.removeItem('adagioScript'); + adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); + + sandbox = sinon.createSandbox(); }); - afterEach(function() { + afterEach(() => { + window.ADAGIO = undefined; + + adagioMock.restore(); utilsMock.restore(); + + sandbox.restore(); }); - describe('inherited functions', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); + 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'); }); - }); - describe('isBidRequestValid', () => { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - let element = { - x: 0, - y: 0, - width: 200, - height: 300, - getBoundingClientRect: () => { - return { - width: element.width, - height: element.height, - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height - }; - } - }; - sandbox.stub(document, 'getElementById').withArgs('banner-atf').returns(element); + 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'); }); + }) - afterEach(function () { - sandbox.restore(); + describe('isBidRequestValid()', function() { + it('should return true when required params have been found', function() { + const bid = new BidRequestBuilder().withParams().build(); + + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - const bid = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }; + 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 bidWithMediaTypes = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code-2', - 'mediaTypes': { - banner: { - sizes: [[300, 250]], - } - }, - sizes: [[300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } + const bid = new BidRequestBuilder({ + params: { placement: 'PAVE_ATF' } + }).build(); - it('should return true when required params found', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); }) - it('should compute a printNumber for the new bid request on same adUnitCode and same pageviewId', () => { - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits).ok; - expect(window.top.ADAGIO.adUnits['adunit-code']).ok; - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(2); + it('should return false if bid.params is missing', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder().build(); + + expect(spec.isBidRequestValid(bid01)).to.equal(false); + sinon.assert.callCount(utils.logWarn, 1); + }); + + it('should use adUnit code for adUnitElementId and placement params', function() { + const bid01 = new BidRequestBuilder({ params: { + organizationId: '1000', + site: 'site-name', + useAdUnitCodeAsPlacement: true, + useAdUnitCodeAsAdUnitElementId: true + }}).build(); + + 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: { + organizationId: '1000', + placement: 'PAVE_ATF' + }}).build(); + + const bid02 = new BidRequestBuilder({ params: { + organizationId: '1000', + site: 'SITE-NAME' + }}).build(); + + const bid03 = new BidRequestBuilder({ params: { + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }}).build(); + + sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ + siteId: '1000' + }); + + const bid04 = new BidRequestBuilder({ params: { placement: 'PAVE_ATF' } }).build(); + + 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); + }); + + 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.'); + }); + }); + + describe('buildRequests()', function() { + const expectedDataKeys = [ + 'id', + 'organizationId', + 'secure', + 'device', + 'site', + 'pageviewId', + 'adUnits', + 'regs', + 'user', + 'schain', + 'prebidVersion', + 'featuresVersion', + 'data' + ]; + + it('groups requests by organizationId', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bid02 = new BidRequestBuilder().withParams().build(); + const bid03 = new BidRequestBuilder().withParams({ + organizationId: '1002' + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest); - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(3); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.organizationId).to.equal('1000'); + expect(requests[0].data.adUnits).to.have.lengthOf(2); - window.top.ADAGIO.pageviewId = 123; - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + expect(requests[1].data.organizationId).to.equal('1002'); + expect(requests[1].data.adUnits).to.have.lengthOf(1); }); - it('should return false when organization params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.organizationId; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + it('should send bid request to ENDPOINT_PB via POST', function() { + 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'); + + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].options.contentType).to.eq('text/plain'); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); - it('should return false when site params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.site; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + it('should enqueue computed features for collect usage', function() { + sandbox.stub(Date, 'now').returns(12345); + + 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); + + adagioMock.verify(); }); - it('should return false when placement params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.placement; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + it('should filter some props in case refererDetection.reachedTop is false', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + } + }).build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].features).to.exist; + expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); - it('should add autodetected adUnitElementId and environment params', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.adUnitElementId; - delete bidTest.params.environment; - sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').returns({divId: 'banner-atf'}); - sandbox.stub(_features, 'getDevice').returns(4); - spec.isBidRequestValid(bidTest) - expect(bidTest.params.adUnitElementId).to.equal('banner-atf'); - expect(bidTest.params.environment).to.equal('mobile'); - }) + describe('With video mediatype', function() { + context('Outstream video', function() { + it('should logWarn if user does not set renderer.backupOnly: true', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + renderer: { + url: 'https://url.tld', + render: () => true + } + } + }, + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bid01], bidderRequest)[0]; - it('should add autodetected tablet environment params', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.environment; - sandbox.stub(_features, 'getDevice').returns(5); - spec.isBidRequestValid(bidTest) - expect(bidTest.params.environment).to.equal('tablet'); - }) + expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); + sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.'); + }); + }); - it('should return false if not in the window.top', () => { - sandbox.stub(utils, 'getWindowTop').throws(); - expect(spec.isBidRequestValid(bid)).to.equal(false); + it('Update mediaTypes.video with OpenRTB options. Validate and sanitize whitelisted OpenRTB', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + api: 5, // will be removed because invalid + playbackmethod: [7], // will be removed because invalid + } + }, + }).withParams({ + // options in video, will overide + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8] + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + context: 'outstream', + playerSize: [[300, 250]], + playerName: 'adagio', + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8], + w: 300, + h: 250 + }; + + 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.withArgs(sinon.match(new RegExp(/^Adagio: The OpenRTB/)))); + }); }); - it('should expose ADAGIO.pbjsAdUnits in window', () => { - spec.isBidRequestValid(bidWithMediaTypes); - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.pbjsAdUnits).ok; - expect(window.top.ADAGIO.pbjsAdUnits).to.have.lengthOf(2); - const adUnitWithMediaTypeSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code-2')[0]; - const adUnitWithSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code')[0]; - expect(adUnitWithMediaTypeSizes.sizes).to.eql([[300, 250]]); - expect(adUnitWithSizes.sizes).to.eql([[300, 250], [300, 600]]); + describe('with sChain', function() { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 + }] + }; + + it('should add the schain if available at bidder level', function() { + const bid01 = new BidRequestBuilder({ schain }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.schain).to.deep.equal(schain); + }); + + it('Schain should not be added to the request', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.schain).to.not.exist; + }); }); - }); - describe('buildRequests', () => { - const sandbox = sinon.createSandbox(); + describe('with GDPR', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - const banner300x250 = { - x: 0, - y: 0, - width: 300, - height: 250, - getBoundingClientRect: () => { + const gdprConsentBuilderTCF1 = function gdprConsentBuilderTCF1(applies, allows) { return { - width: banner300x250.width, - height: banner300x250.height, - left: banner300x250.x, - top: banner300x250.y, - right: banner300x250.x + banner300x250.width, - bottom: banner300x250.y + banner300x250.height + consentString, + gdprApplies: applies, + allowAuctionWithoutConsent: allows, + apiVersion: 1 }; - }, - }; + }; - const banner300x600 = { - x: 0, - y: 0, - width: 300, - height: 600, - getBoundingClientRect: () => { + const gdprConsentBuilderTCF2 = function gdprConsentBuilderTCF2(applies) { return { - width: banner300x600.width, - height: banner300x600.height, - left: banner300x600.x, - top: banner300x600.y, - right: banner300x600.x + banner300x600.width, - bottom: banner300x600.y + banner300x600.height + consentString, + gdprApplies: applies, + apiVersion: 2 }; - }, - }; + }; - const computedStyleBlock = { - display: 'block' - }; + context('When GDPR applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(true, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 1, + apiVersion: 1 + }; - const computedStyleNone = { - display: 'none' - }; + const requests = spec.buildRequests([bid01], bidderRequest); - const stubs = { - topGetElementById: undefined, - topGetComputedStyle: undefined - } + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); + }); - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.adUnits = top.ADAGIO.adUnits || {}; - top.ADAGIO.pbjsAdUnits = top.ADAGIO.pbjsAdUnits || []; + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(true) + }).build(); - beforeEach(function () { - stubs.topGetElementById = sandbox.stub(top.document, 'getElementById'); - stubs.topGetComputedStyle = sandbox.stub(top, 'getComputedStyle'); + const expected = { + consentString, + allowAuctionWithoutConsent: 0, + consentRequired: 1, + apiVersion: 2 + }; - stubs.topGetElementById.withArgs('banner-atf-123').returns(banner300x250); - stubs.topGetElementById.withArgs('banner-atf-456').returns(banner300x600); - stubs.topGetElementById.withArgs('does-not-exist').returns(null); - stubs.topGetComputedStyle.returns(computedStyleBlock); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - afterEach(function () { - sandbox.restore(); - }); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); + }); + }); - after(function() { - sandbox.reset(); - }) + context('When GDPR does not applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(false, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 0, + apiVersion: 1 + }; - let bidRequests = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456' - }, - 'adUnitCode': 'adunit-code3', - 'mediaTypes': { - banner: { - sizes: [[300, 250]] - } - }, - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } - ]; + const requests = spec.buildRequests([bid01], bidderRequest); - const bidRequestsWithPostBid = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456', - postBid: true - }, - 'adUnitCode': 'adunit-code3', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } - ]; + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); + }); - let consentString = 'theConsentString'; - let bidderRequest = { - 'bidderCode': 'adagio', - 'auctionId': '12jejebn', - 'bidderRequestId': 'hehehehbeheh', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - allowAuctionWithoutConsent: true, - apiVersion: 1, - }, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'http://test.io/index.html?pbjs_debug=true' - } - }; + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(false) + }).build(); - let bidderRequestTCF2 = { - 'bidderCode': 'adagio', - 'auctionId': '12jejebn', - 'bidderRequestId': 'hehehehbeheh', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - vendorData: { - tcString: consentString, - gdprApplies: true - }, - gdprApplies: true, - apiVersion: 2 - }, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'http://test.io/index.html?pbjs_debug=true' - } - }; + const expected = { + consentString, + consentRequired: 0, + allowAuctionWithoutConsent: 0, + apiVersion: 2 + }; - it('groups requests by siteId', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.organizationId).to.equal('123'); - expect(requests[0].data.adUnits).to.have.lengthOf(2); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); + }); + }); - expect(requests[1].data.organizationId).to.equal('456'); - expect(requests[1].data.adUnits).to.have.lengthOf(1); - }); + context('When GDPR is undefined in bidderRequest', function() { + it('send an empty data.gdpr to the server', function() { + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); - it('sends bid request to ENDPOINT_PB via POST', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); - expect(request.data.prebidVersion).to.equal('$prebid.version$'); + expect(requests[0].data.regs.gdpr).to.be.empty; + }); + }); }); - it('features params "adunit_position" must be empty if adUnitElement is not found in the DOM', () => { - const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {adUnitElementId: 'does-not-exist'}})], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.adunit_position).to.deep.equal(''); - }); + describe('with COPPA', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - it('features params "adunit_position" should be computed even if DOM element is display:none', () => { - stubs.topGetComputedStyle.returns(computedStyleNone); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.adunit_position).to.equal('0x0'); - }); + it('should send the Coppa "required" flag set to "1" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); - it('features params "viewport" should be computed even if window.innerWidth is not supported', () => { - sandbox.stub(top, 'innerWidth').value(undefined); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); - }); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); - it('AdUnit requested should have the correct sizes array depending on the config', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[1].data.adUnits[0]).to.have.property('mediaTypes'); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('features params must be an object if featurejs is loaded', () => { - let requests = spec.buildRequests(bidRequests, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; + expect(requests[0].data.regs.coppa.required).to.equal(1); + + config.getConfig.restore(); + }); }); - it('outerAdUnitElementId must be added when PostBid param has been set', () => { - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.pbjsAdUnits = []; + describe('without COPPA', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - top.ADAGIO.pbjsAdUnits.push({ - code: bidRequestsWithPostBid[0].adUnitCode, - sizes: bidRequestsWithPostBid[0].sizes, - bids: [{ - bidder: bidRequestsWithPostBid[0].bidder, - params: bidRequestsWithPostBid[0].params - }] + it('should send the Coppa "required" flag set to "0" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.coppa.required).to.equal(0); }); - let requests = spec.buildRequests(bidRequestsWithPostBid, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].params.outerAdUnitElementId).to.exist; - top.ADAGIO.pbjsAdUnits = undefined; }); - it('generates a pageviewId if missing', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - delete window.top.ADAGIO.pageviewId; + describe('with USPrivacy', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + const consent = 'Y11N'; + + it('should send the USPrivacy "ccpa.uspConsent" in the request', function () { + const bidderRequest = new BidderRequestBuilder({ + uspConsent: consent + }).build(); - expect(requests[0].data.pageviewId).to.exist.and.to.not.equal('_').and.to.not.equal(''); - expect(requests[0].data.pageviewId).to.equal(requests[1].data.pageviewId); + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.ccpa.uspConsent).to.equal(consent); + }); }); - it('uses an existing pageviewId if present', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; + describe('without USPrivacy', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + it('should have an empty "ccpa" field in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); - expect(requests[0].data.pageviewId).to.equal('abc'); - expect(requests[1].data.pageviewId).to.equal('abc'); + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.ccpa).to.be.empty; + }); }); - it('should send the printNumber in features object', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; - window.top.ADAGIO.adUnits['adunit-code1'] = { - pageviewId: 'abc', - printNumber: 2 + describe('with userID modules', function() { + const userId = { + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', + unsuported: '666' }; - const requests = spec.buildRequests([bidRequests[0]], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].features.print_number).to.equal('2'); - }); - it('organizationId param key must be a string', () => { - const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {organizationId: 1010}})], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].params).to.exist; - expect(request.data.adUnits[0].params.organizationId).to.deep.equal('1010'); - expect(request.data.organizationId).to.exist; - expect(request.data.organizationId).to.deep.equal('1010'); - }); + it('should send "user.eids" in the request for Prebid.js supported modules only', function() { + const bid01 = new BidRequestBuilder({ + userId + }).withParams().build(); - it('GDPR consent is applied', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); + const bidderRequest = new BidderRequestBuilder().build(); - it('GDPR consent is applied w/ TCF2', () => { - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('GDPR consent is not applied', () => { - bidderRequest.gdprConsent.gdprApplies = false; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); + const expected = [{ + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; - it('GDPR consent is not applied w/ TCF2', () => { - bidderRequestTCF2.gdprConsent.gdprApplies = false; - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); - - it('GDPR consent is undefined', () => { - delete bidderRequest.gdprConsent.consentString; - delete bidderRequest.gdprConsent.gdprApplies; - delete bidderRequest.gdprConsent.allowAuctionWithoutConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.not.have.property('consentString'); - expect(request.data.gdpr).to.not.have.property('gdprApplies'); - expect(request.data.gdpr).to.not.have.property('allowAuctionWithoutConsent'); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); - - it('GDPR consent is undefined w/ TCF2', () => { - delete bidderRequestTCF2.gdprConsent.consentString; - delete bidderRequestTCF2.gdprConsent.gdprApplies; - delete bidderRequestTCF2.gdprConsent.vendorData; - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.not.have.property('consentString'); - expect(request.data.gdpr).to.not.have.property('gdprApplies'); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); + expect(requests[0].data.user.eids).to.have.lengthOf(1); + expect(requests[0].data.user.eids).to.deep.equal(expected); + }); - it('GDPR consent bidderRequest does not have gdprConsent', () => { - delete bidderRequest.gdprConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.be.empty; - }); + it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { + const bid01 = new BidRequestBuilder({ + userId: { + unsuported: '666' + } + }).withParams().build(); - it('should expose version in window', () => { - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.versions).ok; - expect(window.top.ADAGIO.versions.adagioBidderAdapter).to.eq(VERSION); - }); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); - it('should returns an empty array if the bidder cannot access to window top (based on refererInfo.reachedTop)', () => { - const requests = spec.buildRequests(bidRequests, { - ...bidderRequest, - refererInfo: { reachedTop: false } + expect(requests[0].data.user.eids).to.be.empty; }); - expect(requests).to.be.empty; }); - it('Should add the schain if available at bidder level', () => { - const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'ssp.test', - sid: '00001', - hp: 1 - }] + 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'}); }); - const requests = spec.buildRequests([bidRequest], bidderRequest); - const request = requests[0]; + 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(request.data.schain).to.exist; - expect(request.data.schain).to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'ssp.test', - sid: '00001', - hp: 1 - }] + 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('Schain should not be added to the request', () => { - const requests = spec.buildRequests([bidRequests[0]], bidderRequest); - const request = requests[0]; - expect(request.data.schain).to.not.exist; + 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', () => { - const sandbox = sinon.createSandbox(); - + describe('interpretResponse()', function() { let serverResponse = { body: { data: { pred: 1 }, - bids: [ - { - ad: '
', - cpm: 1, - creativeId: 'creativeId', - currency: 'EUR', - height: 250, - netRevenue: true, - requestId: 'c180kg4267tyqz', - ttl: 360, - width: 300 + bids: [{ + ad: '
', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300, + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserId: '80', + advertiserName: 'An Advertiser', + networkId: '110' } - ] - } - }; - - let emptyBodyServerResponse = { - body: null - }; - - let withoutBidsArrayServerResponse = { - body: { - bids: [] - } - }; - - let serverResponseWhichThrowsException = { - body: { - data: { - pred: 1 - }, - bids: { - foo: 'bar' - } + }] } }; let bidRequest = { - 'data': { - 'adUnits': [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - adUnitElementId: 'banner-atf-456', - pagetype: 'ARTICLE', - category: 'NEWS', - subcategory: 'SPORT', - environment: 'SITE-MOBILE' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - 'pageviewId': 'd8c4fl2k39i0wn', - } - ] + data: { + adUnits: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'desktop', + supportIObs: true + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { sizes: [[300, 250], [300, 600]] } + }, + bidId: 'c180kg4267tyqz', + bidderRequestId: '8vfscuixrovn8i', + auctionId: 'lel4fhp239i9km', + pageviewId: 'd8c4fl2k39i0wn', + }] } }; - afterEach(function() { - sandbox.restore(); - }); + it('should return an empty response array if body is empty', function() { + expect(spec.interpretResponse({ + body: null + }, bidRequest)).to.be.an('array').length(0); - it('Should returns empty response if body is empty', () => { - expect(spec.interpretResponse(emptyBodyServerResponse, bidRequest)).to.be.an('array').length(0); - expect(spec.interpretResponse({body: {}}, bidRequest)).to.be.an('array').length(0); + expect(spec.interpretResponse({ + body: {} + }, bidRequest)).to.be.an('array').length(0); }); - it('Should returns empty response if bids array is empty', () => { - expect(spec.interpretResponse({withoutBidsArrayServerResponse}, bidRequest)).to.be.an('array').length(0); + it('should return an empty response array if bids array is empty', function() { + expect(spec.interpretResponse({ + body: { + bids: [] + } + }, bidRequest)).to.be.an('array').length(0); }); - it('should get correct bid response', () => { + it('should handle properly a correct bid response', function() { let expectedResponse = [{ ad: '
', cpm: 1, @@ -683,87 +816,730 @@ describe('adagioAdapter', () => { requestId: 'c180kg4267tyqz', ttl: 360, width: 300, - placement: 'PAVE_ATF-456', - site: 'ADAGIO-456', + placement: 'PAVE_ATF', + site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'SITE-MOBILE' + 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('Should populate ADAGIO queue with ssp-data', () => { - spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); + 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 not populate ADAGIO queue with ssp-data if not in top window', () => { - utils.getWindowTop().ADAGIO.queue = []; - sandbox.stub(utils, 'getWindowTop').throws(); + it('should populate ADAGIO queue with ssp-data', function() { + sandbox.stub(Date, 'now').returns(12345); + + adagioMock.expects('enqueue').withExactArgs({ + action: 'ssp-data', + ts: 12345, + data: serverResponse.body.data + }).once(); + spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); - expect(window.top.ADAGIO.queue).empty; + + adagioMock.verify(); }); - it('should return an empty response even if an exception is ', () => { - expect(spec.interpretResponse(serverResponseWhichThrowsException, bidRequest)).to.be.an('array').length(0); + it('should properly try-catch an exception and return an empty array', function() { + sandbox.stub(adagio, 'enqueue').throws(); + utilsMock.expects('logError').once(); + + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); + + utilsMock.verify(); + }); + + describe('Response with video outstream', () => { + const bidRequestWithOutstream = utils.deepClone(bidRequest); + bidRequestWithOutstream.data.adUnits[0].mediaTypes.video = { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + skip: true + }; + + const serverResponseWithOutstream = utils.deepClone(serverResponse); + serverResponseWithOutstream.body.bids[0].vastXml = ''; + serverResponseWithOutstream.body.bids[0].mediaType = 'video'; + serverResponseWithOutstream.body.bids[0].outstream = { + bvwUrl: 'https://foo.baz', + impUrl: 'https://foo.bar' + }; + + it('should set a renderer in video outstream context', function() { + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + expect(bidResponse).to.have.any.keys('outstream', 'renderer', 'mediaType'); + expect(bidResponse.renderer).to.be.a('object'); + expect(bidResponse.renderer.url).to.equal(RENDERER_URL); + expect(bidResponse.renderer.config.bvwUrl).to.be.ok; + expect(bidResponse.renderer.config.impUrl).to.be.ok; + expect(bidResponse.renderer.loaded).to.not.be.ok; + expect(bidResponse.width).to.equal(300); + 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', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clicktrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + 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: {} + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); }); }); - describe('getUserSyncs', () => { + describe('getUserSyncs()', function() { const syncOptions = { - 'iframeEnabled': 'true' - } - const serverResponses = [ - { + syncEnabled: false + }; + + it('should handle user syncs if data is in the server response ', function() { + const serverResponses = [{ body: { userSyncs: [ - { - t: 'i', - u: 'https://test.url.com/setuid' - }, - { - t: 'p', - u: 'https://test.url.com/setuid' - } + { t: 'i', u: 'https://test.url.com/setuid' }, + { t: 'p', u: 'https://test.url.com/setuid' } ] } - } - ]; - - const emptyServerResponses = [ - { - body: '' - } - ]; + }]; - it('should handle correctly user syncs', () => { let result = spec.getUserSyncs(syncOptions, serverResponses); - let emptyResult = spec.getUserSyncs(syncOptions, emptyServerResponses); + expect(result[0].type).to.equal('iframe'); expect(result[0].url).contain('setuid'); + expect(result[1].type).to.equal('image'); - expect(emptyResult).to.equal(false); + expect(result[1].url).contain('setuid'); + }); + + it('should return false if data is not in server response', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.equal(false); + }); + }); + + 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': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + 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]+$/); + expect(result.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + 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.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', + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }, + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + 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'); + 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'); }); }); - describe('adagioScriptFromLocalStorageCb', () => { + 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': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; + + 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; + }); + + 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(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; + + 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; + }); + + it('should return all expected features when prebid safeframe api not properly implemented bis', function() { + window.$sf.ext.geom = undefined; + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; + + 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('Adagio features when prebid in crossdomain iframe', function() { + it('should return all expected features', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const result = requests[0].data.adUnits[0].features; + + 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.skip('optional params auto detection', function() { + it('should auto detect environment', function() { + const getDeviceStub = sandbox.stub(_features, 'getDevice'); + + getDeviceStub.returns(5); + expect(adagio.autoDetectEnvironment()).to.eq('tablet'); + + getDeviceStub.returns(4); + expect(adagio.autoDetectEnvironment()).to.eq('mobile'); + + getDeviceStub.returns(2); + expect(adagio.autoDetectEnvironment()).to.eq('desktop'); + }); + + it('should auto detect adUnitElementId when GPT is used', function() { + sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); + expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); + }); + }); + + 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); + }); + + it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { + sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); + + window.top.ADAGIO.adUnits['adunit-code'] = { + pageviewId: 'abc-def', + printNumber: 1, + }; + + expect(adagio.computePrintNumber('adunit-code')).to.eql(2); + }); + }); + + describe('site information using refererDetection or window.top', function() { + it('should returns domain, page and window.referrer in a window.top context', function() { + sandbox.stub(utils, 'getWindowTop').returns({ + location: { + hostname: 'test.io', + href: 'https://test.io/article/a.html' + }, + document: { + referrer: 'https://google.com' + } + }); + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'test.io', + page: 'https://test.io/article/a.html', + referrer: 'https://google.com' + }); + }); + + it('should returns domain and page in a cross-domain w/ top domain reached context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 0, + reachedTop: true, + referer: 'http://level.io/', + stack: [ + 'http://level.io/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'level.io', + page: 'http://level.io/', + referrer: '' + }); + }); + + it('should not return anything in a cross-domain w/o top domain reached and w/o ancestor context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: '', + page: '', + referrer: '' + }); + }); + + it('should return domain only in a cross-domain w/o top domain reached and w/ ancestors context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + 'http://mytest.com/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'mytest.com', + page: '', + referrer: '' + }); + }); + }); + + describe('adagioScriptFromLocalStorageCb()', function() { const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; const INVALID_HASH = 'invalid'; const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; + beforeEach(function() { + localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + }); + + describe('getAdagioScript', function() { + it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() { + sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb'); + const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + getAdagioScript(); + + sinon.assert.callCount(getDataFromLocalStorageStub, 1); + sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1); + }); + + it('should load external script if the user consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.true; + }); + + it('should not load external script if the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + }); + + it('should remove the localStorage key if exists and the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script'); + + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + }); + }); + it('should verify valid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -774,9 +1550,9 @@ describe('adagioAdapter', () => { it('should verify valid hash with invalid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -787,9 +1563,9 @@ describe('adagioAdapter', () => { it('should verify invalid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -800,14 +1576,21 @@ describe('adagioAdapter', () => { it('should verify missing hash', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; utilsMock.verify(); }); + + it('should return false if content script does not exist in localStorage', function() { + sandbox.spy(utils, 'logWarn'); + expect(adagioScriptFromLocalStorageCb(null)).to.be.undefined; + sinon.assert.callCount(utils.logWarn, 1); + sinon.assert.calledWith(utils.logWarn, 'Adagio: script not found.'); + }); }); }); diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js new file mode 100755 index 00000000000..a6b8a794eeb --- /dev/null +++ b/test/spec/modules/adbookpspBidAdapter_spec.js @@ -0,0 +1,1323 @@ +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_adid_c_adbookpsp: '5', + hb_deal_adbookpsp: 'werwetwerw', + hb_liid_adbookpsp: '2342345', + }, + referrer: 'http://prebid-test-page.io:8080/banner.html', + lineItemId: '2342345', + }, + { + ad: '', + adId: '10', + adUnitCode: 'div-gpt-ad-837465923534-0', + adserverTargeting: { + hb_adid_c_adbookpsp: '10', + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + }, + 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_deal_adbookpsp: 'werwetwerw', + hb_liid_adbookpsp: '2342345', + hb_adid_c_adbookpsp: '5', + }); + expect(bids[1].adserverTargeting).to.deep.equal({ + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + hb_adid_c_adbookpsp: '10', + }); + }); + + 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_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', + hb_liid_adbookpsp: '2342345,2121221', + hb_adid_c_adbookpsp: '5,10', + }); + } + }); + + 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_adid_c_adbookpsp: '10', + hb_deal_adbookpsp: 'dsfxcxcvxc', + hb_liid_adbookpsp: '2121221', + }); + }); + + 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', + }, + 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', + }, + 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 a9b56ade79e..00000000000 --- a/test/spec/modules/adbutlerBidAdapter_spec.js +++ /dev/null @@ -1,221 +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' - }, - 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 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/addefendBidAdapter_spec.js b/test/spec/modules/addefendBidAdapter_spec.js new file mode 100644 index 00000000000..ac01750e98f --- /dev/null +++ b/test/spec/modules/addefendBidAdapter_spec.js @@ -0,0 +1,184 @@ +import {expect} from 'chai'; +import {spec} from 'modules/addefendBidAdapter.js'; + +describe('addefendBidAdapter', () => { + const defaultBidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + sizes: [[300, 250], [300, 600]], + params: { + pageId: 'stringid1', + placementId: 'stringid2' + } + }; + + const deepClone = function (val) { + return JSON.parse(JSON.stringify(val)); + }; + + const buildRequest = (buildRequest, bidderRequest) => { + if (!Array.isArray(buildRequest)) { + buildRequest = [buildRequest]; + } + + return spec.buildRequests(buildRequest, { + ...bidderRequest || {}, + refererInfo: { + referer: 'https://referer.example.com' + } + })[0]; + }; + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('pageId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.pageId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('placementId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.placementId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequest); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest); + + it('sends bid request to endpoint via https using post', () => { + expect(request.method).to.equal('POST'); + expect(request.url.indexOf('https://')).to.equal(0); + expect(request.url).to.equal(`${spec.hostname}/bid`); + }); + + it('contains prebid version parameter', () => { + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); + }); + + it('contains correct referer', () => { + expect(request.data.referer).to.equal('https://referer.example.com'); + }); + + it('contains auctionId', () => { + expect(request.data.auctionId).to.equal('ccc4c7cdfe11cfbd74065e6dd28413d8'); + }); + + it('contains pageId', () => { + expect(request.data.pageId).to.equal('stringid1'); + }); + + it('sends correct bid parameters', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(request.data.bids).to.deep.equal([ { + bidId: bidRequest.bidId, + placementId: bidRequest.params.placementId, + sizes: [ '300x250', '300x600' ], + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6' + } ]); + }); + + it('handles empty gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: {} + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles non-existent gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: null + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles properly filled gdpr string', () => { + const bidRequest = deepClone(defaultBidRequest); + const consentString = 'GDPR_CONSENT_STRING'; + const request = buildRequest(bidRequest, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + expect(request.data.gdpr_consent).to.be.equal(consentString); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 120, + 'netRevenue': true, + 'currency': 'EUR', + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const expectedResponse = [ + { + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 120, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true, + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.deep.equal(expectedResponse[0][key]); + }); + }); + + it('handles incomplete server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 60 + } + ]; + 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/adformOpenRTBBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js similarity index 72% rename from test/spec/modules/adformOpenRTBBidAdapter_spec.js rename to test/spec/modules/adfBidAdapter_spec.js index 05788183e29..ef11490529a 100644 --- a/test/spec/modules/adformOpenRTBBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -1,14 +1,24 @@ // jshint esversion: 6, es3: false, node: true -import {assert, expect} from 'chai'; -import {spec} from 'modules/adformOpenRTBBidAdapter.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('AdformOpenRTB adapter', function () { - let serverResponse, bidRequest, bidResponses; +describe('Adf adapter', function () { let bids = []; + describe('backwards-compatibility', function () { + it('should have adformOpenRTB alias defined', 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 () { let bid = { 'bidder': 'adformOpenRTB', @@ -28,11 +38,13 @@ describe('AdformOpenRTB adapter', function () { }); 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' } }]; @@ -46,7 +58,7 @@ describe('AdformOpenRTB 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); @@ -56,7 +68,7 @@ describe('AdformOpenRTB 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); @@ -65,7 +77,7 @@ describe('AdformOpenRTB 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); @@ -111,7 +123,7 @@ describe('AdformOpenRTB 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); @@ -144,26 +156,66 @@ describe('AdformOpenRTB 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' }); }); @@ -193,6 +245,27 @@ describe('AdformOpenRTB 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 = [{ @@ -206,7 +279,7 @@ describe('AdformOpenRTB 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); @@ -230,13 +303,16 @@ describe('AdformOpenRTB 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; @@ -246,21 +322,106 @@ describe('AdformOpenRTB 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('multiple media types', function () { + it('should use single media type 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: {} + } + }]; + let [ banner, video, native ] = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + + assert.ok(banner.banner); + assert.equal(banner.video, undefined); + assert.equal(banner.native, undefined); + assert.ok(video.video); + assert.equal(video.banner, undefined); + assert.equal(video.native, undefined); + assert.ok(native.native); + assert.equal(native.video, undefined); + assert.equal(native.banner, undefined); + }); + }); + + 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'] }, @@ -276,7 +437,7 @@ describe('AdformOpenRTB 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'] }, @@ -296,7 +457,7 @@ describe('AdformOpenRTB 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] }, @@ -323,7 +484,7 @@ describe('AdformOpenRTB 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]] @@ -341,7 +502,7 @@ describe('AdformOpenRTB adapter', function () { it('should utilise aspect_ratios', function () { const validBidRequests = [{ bidId: 'bidId', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, nativeParams: { image: { aspect_ratios: [{ @@ -373,7 +534,7 @@ describe('AdformOpenRTB 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: [] @@ -391,7 +552,7 @@ describe('AdformOpenRTB 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: [{ @@ -434,7 +595,7 @@ describe('AdformOpenRTB 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'] }, @@ -443,7 +604,7 @@ describe('AdformOpenRTB 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'] }, @@ -475,7 +636,7 @@ describe('AdformOpenRTB 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'] }, @@ -484,7 +645,7 @@ describe('AdformOpenRTB 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'] }, @@ -493,7 +654,7 @@ describe('AdformOpenRTB 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'] }, @@ -502,7 +663,7 @@ describe('AdformOpenRTB 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'] }, @@ -536,7 +697,9 @@ describe('AdformOpenRTB adapter', function () { assets: [], link: { url: 'link' }, imptrackers: ['imptrackers url1', 'imptrackers url2'] - } + }, + dealid: 'deal-id', + adomain: [ 'demo.com' ] } ] }], @@ -548,7 +711,8 @@ describe('AdformOpenRTB adapter', function () { bids: [ { bidId: 'bidId1', - params: { siteId: 'siteId', mid: 1000 }, + params: { mid: 1000 }, + mediaType: 'native', nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -567,7 +731,9 @@ describe('AdformOpenRTB 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].bidderCode, 'adformOpenRTB'); + 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 = [ @@ -672,5 +838,96 @@ describe('AdformOpenRTB 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: '' }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 }, + mediaType: 'banner' + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].ad, ''); + }); + }); + + describe('video', function () { + it('should set vastXml on response', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '' }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 }, + mediaType: 'video' + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].vastXml, ''); + }); + + it('should add renderer for outstream bids', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '' }, { impid: '2', adm: '' }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 }, + mediaType: 'video', + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + { + bidId: 'bidId2', + params: { mid: 1000 }, + mediaType: 'video', + 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 360979659de..00000000000 --- a/test/spec/modules/adformBidAdapter_spec.js +++ /dev/null @@ -1,510 +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] - } - }); - }); - - 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.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..7eacba2f7d4 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.1.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.1.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.1.0&tp=https%3A%2F%2Fexample.com' }; it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -198,165 +198,497 @@ describe('AdgenerationAdapter', function () { 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: '
'}, + ], + adomain: ['advertiserdomain.com'] + }, + native: { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + 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 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 + 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' }, - { - 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 + }, + results: [ + {ad: 'Creative<\/body>'} + ], + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + } + }, + 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: '
'}, + ], + adomain: [] + }, + native: { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + adomain: [], + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/#' + }, + id: 502 }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 + { + 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' }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } + }, + 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: '
'}, ], - 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' + }, + native: { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + 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 + } ], - 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 + 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 + } + }, + 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 () { @@ -365,43 +697,133 @@ describe('AdgenerationAdapter', function () { }); 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); + const result = spec.interpretResponse({body: serverResponse.normal.banner}, bidRequests.banner)[0]; + expect(result.requestId).to.equal(bidResponses.normal.banner.requestId); + expect(result.width).to.equal(bidResponses.normal.banner.width); + expect(result.height).to.equal(bidResponses.normal.banner.height); + expect(result.creativeId).to.equal(bidResponses.normal.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.banner.dealId); + expect(result.currency).to.equal(bidResponses.normal.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.banner.ttl); + expect(result.ad).to.equal(bidResponses.normal.banner.ad); + expect(result.meta.advertiserDomains).deep.equal(bidResponses.normal.banner.adomain); }); 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); + const result = spec.interpretResponse({body: serverResponse.normal.native}, bidRequests.native)[0]; + expect(result.requestId).to.equal(bidResponses.normal.native.requestId); + expect(result.width).to.equal(bidResponses.normal.native.width); + expect(result.height).to.equal(bidResponses.normal.native.height); + expect(result.creativeId).to.equal(bidResponses.normal.native.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.native.dealId); + expect(result.currency).to.equal(bidResponses.normal.native.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.native.ttl); + expect(result.native.title).to.equal(bidResponses.normal.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.normal.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.normal.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.normal.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.normal.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.normal.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.normal.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.normal.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.normal.native.native.body); + expect(result.native.cta).to.equal(bidResponses.normal.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.normal.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.normal.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.normal.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.normal.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.normal.native.mediaType); + expect(result.meta.advertiserDomains).deep.equal(bidResponses.normal.native.adomain); + }); + + 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 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/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index def504b0424..78ca25d6be2 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -17,10 +17,9 @@ let minimalBid = function() { } }; -let bidWithParams = function(data, userId) { +let bidWithParams = function(data) { let bid = minimalBid(); bid.params.data = data; - bid.userId = userId; return bid; }; @@ -71,40 +70,71 @@ describe('AdheseAdapter', function () { } }; + it('should include requested slots', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).slots[0].slotname).to.equal('_main_page_-leaderboard'); + }); + it('should include all extra bid params', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25'); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }); }); - it('should include duplicate bid params once', function () { + it('should assign bid params per slot', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25/cigent'); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }).and.not.to.deep.include({ 'ci': [ 'gent' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ag': [ '25' ] }).and.to.deep.include({ 'ci': [ 'gent' ] }); }); it('should split multiple target values', function () { let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/cilondon;gent'); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ci': [ 'london' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ci': [ 'gent' ] }); + }); + + it('should filter out empty params', function () { + let req = spec.buildRequests([ bidWithParams({ 'aa': [], 'bb': null, 'cc': '', 'dd': [ '', '' ], 'ee': [ 0, 1, null ], 'ff': 0, 'gg': [ 'x', 'y', '' ] }) ], bidderRequest); + + let params = JSON.parse(req.data).slots[0].parameters; + expect(params).to.not.have.any.keys('aa', 'bb', 'cc', 'dd'); + expect(params).to.deep.include({ 'ee': [ 0, 1 ], 'ff': [ 0 ], 'gg': [ 'x', 'y' ] }); }); it('should include gdpr consent param', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xtCONSENT_STRING'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xt': [ 'CONSENT_STRING' ] }); }); it('should include referer param in base64url format', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xfaHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xf': [ 'aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ' ] }); }); - it('should include id5 id as /x5 param', function () { - let req = spec.buildRequests([ bidWithParams({}, {'id5id': 'ID5-1234567890'}) ], bidderRequest); + it('should include eids', function () { + let bid = minimalBid(); + bid.userIdAsEids = [{ source: 'id5-sync.com', uids: [{ id: 'ID5@59sigaS-...' }] }]; - expect(req.url).to.contain('/x5ID5-1234567890'); + let req = spec.buildRequests([ bid ], bidderRequest); + + expect(JSON.parse(req.data).user.ext.eids).to.deep.equal(bid.userIdAsEids); + }); + + it('should not include eids field when userid module disabled', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data)).to.not.have.key('eids'); + }); + + it('should request vast content as url', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).vastContentAsUrl).to.equal(true); }); it('should include bids', function () { @@ -113,6 +143,18 @@ describe('AdheseAdapter', function () { expect(req.bids).to.deep.equal([ bid ]); }); + + it('should make a POST request', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.method).to.equal('POST'); + }); + + it('should request the json endpoint', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.url).to.equal('https://ads-demo.adhese.com/json'); + }); }); describe('interpretResponse', () => { @@ -144,7 +186,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'}}} + extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}, mediaType: 'banner'}, + adomain: [ + 'www.example.com' + ] } ] }; @@ -175,7 +220,12 @@ describe('AdheseAdapter', function () { slotId: '10', slotName: '_main_page_-leaderboard' } - } + }, + meta: { + advertiserDomains: [ + 'www.example.com' + ] + }, }]; expect(spec.interpretResponse(sspBannerResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -191,7 +241,7 @@ describe('AdheseAdapter', function () { width: '640', height: '350', body: '', - extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}} + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} } ] }; @@ -211,11 +261,55 @@ describe('AdheseAdapter', function () { adhese: { origin: 'RUBICON', originInstance: '', - originData: {} } + originData: {} + }, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('should get correct ssp cache video response', () => { + let sspCachedVideoResponse = { + body: [ + { + origin: 'RUBICON', + ext: 'js', + slotName: '_main_page_-leaderboard', + adType: 'leaderboard', + width: '640', + height: '350', + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + cpm: 2.1, + currency: 'USD', + creativeId: 'RUBICON', + dealId: '', + width: 640, + height: 350, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + adhese: { + origin: 'RUBICON', + originInstance: '', + originData: {} + }, + meta: { + advertiserDomains: [] + }, + }]; + expect(spec.interpretResponse(sspCachedVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should get correct Adhese banner response', () => { const adheseBannerResponse = { body: [ @@ -253,7 +347,8 @@ describe('AdheseAdapter', function () { amount: '5.96', currency: 'USD' } - } + }, + mediaType: 'banner' } } ] @@ -288,6 +383,9 @@ describe('AdheseAdapter', function () { mediaType: 'banner', netRevenue: NET_REVENUE, ttl: TTL, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(adheseBannerResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -315,7 +413,10 @@ describe('AdheseAdapter', function () { impressionCounter: 'https://hosts-demo.adhese.com/track/742898', origin: 'JERLICIA', originData: {}, - auctionable: true + auctionable: true, + extension: { + mediaType: 'video' + } } ] }; @@ -349,6 +450,76 @@ describe('AdheseAdapter', function () { mediaType: 'video', netRevenue: NET_REVENUE, ttl: TTL, + meta: { + advertiserDomains: [] + }, + }]; + expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + + it('should get correct Adhese cached video response', () => { + const adheseVideoResponse = { + body: [ + { + adType: 'preroll', + adFormat: '', + orderId: '22248', + adspaceId: '164196', + body: '', + height: '360', + width: '640', + extension: { + mediaType: 'video' + }, + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + libId: '89860', + id: '742470', + advertiserId: '2263', + ext: 'advar', + orderName: 'Smartphoto EOY-20181112', + creativeName: 'PREROLL', + slotName: '_main_page_-leaderboard', + slotID: '41711', + impressionCounter: 'https://hosts-demo.adhese.com/track/742898', + origin: 'JERLICIA', + originData: {}, + auctionable: true + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + adhese: { + origin: '', + originInstance: '', + originData: { + adFormat: '', + adId: '742470', + adType: 'preroll', + adspaceId: '164196', + libId: '89860', + orderProperty: undefined, + priority: undefined, + viewableImpressionCounter: undefined, + slotId: '41711', + slotName: '_main_page_-leaderboard', + advertiserId: '2263', + } + }, + cpm: 0, + currency: 'USD', + creativeId: '742470', + dealId: '22248', + width: 640, + height: 360, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + meta: { + advertiserDomains: [] + }, }]; expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); diff --git a/test/spec/modules/adkernelAdnAnalytics_spec.js b/test/spec/modules/adkernelAdnAnalytics_spec.js index e7ef831080c..7af96c9321c 100644 --- a/test/spec/modules/adkernelAdnAnalytics_spec.js +++ b/test/spec/modules/adkernelAdnAnalytics_spec.js @@ -1,6 +1,6 @@ -import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter.js'; +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; import {expect} from 'chai'; -import adapterManager from 'src/adapterManager.js'; +import adapterManager from 'src/adapterManager'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); @@ -158,11 +158,16 @@ describe('', function () { sizes: [[300, 250]], bidId: '208750227436c1', bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + gdprConsent: { + consentString: 'CONSENT', + gdprApplies: true, + apiVersion: 2 + }, + uspConsent: '1---' }], auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 + timeout: 3000 }; const RESPONSE = { @@ -202,6 +207,10 @@ describe('', function () { events.getEvents.restore(); }); + after(function() { + sandbox.restore(); + }); + it('should be configurable', function () { adapterManager.registerAnalyticsAdapter({ code: 'adkernelAdn', @@ -221,7 +230,7 @@ describe('', function () { }); it('should handle auction init event', function () { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, bidderRequests: [REQUEST], timeout: 3000}); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); expect(ev[0]).to.be.eql({event: 'auctionInit'}); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 3d3e64aeec9..c4ad134711a 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelAdnBidAdapter.js'; +import {spec} from 'modules/adkernelAdnBidAdapter'; +import {config} from 'src/config'; describe('AdkernelAdn adapter', function () { const bid1_pub1 = { @@ -106,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', @@ -114,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', @@ -205,7 +216,6 @@ describe('AdkernelAdn adapter', function () { fullBidderRequest.bids = bidRequests; let pbRequests = spec.buildRequests(bidRequests, fullBidderRequest); let tagRequests = pbRequests.map(r => JSON.parse(r.data)); - return [pbRequests, tagRequests]; } @@ -263,6 +273,26 @@ describe('AdkernelAdn adapter', function () { expect(bidRequests[0].user).to.have.property('gdpr', 0); expect(bidRequests[0].user).to.not.have.property('consent'); }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_pub1]); + config.resetConfig(); + expect(bidRequests[0]).to.have.property('user'); + expect(bidRequests[0].user).to.have.property('coppa', 1); + }); + + it('should set bidfloor if configured', function() { + let bid = Object.assign({}, bid1_pub1); + bid.getFloor = function() { + return { + currency: 'USD', + floor: 0.145 + } + }; + let [, tagRequests] = buildRequest([bid]); + expect(tagRequests[0].imp[0]).to.have.property('bidfloor', 0.145); + }); }); describe('video request building', () => { @@ -355,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 71637781f24..ab1c5501bd9 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelBidAdapter.js'; -import * as utils from 'src/utils.js'; +import {spec} from 'modules/adkernelBidAdapter'; +import * as utils from 'src/utils'; import {NATIVE, BANNER, VIDEO} from 'src/mediaTypes'; -import {config} from 'src/config.js'; +import {config} from 'src/config'; describe('Adkernel adapter', function () { const bid1_zone1 = { @@ -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'}, @@ -171,10 +179,10 @@ describe('Adkernel adapter', function () { nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', adm: '', w: 300, - h: 250 + h: 250, + dealid: 'deal' }] }], - cur: 'USD', ext: { adk_usersync: [{type: 1, url: 'https://adk.sync.com/sync'}] } @@ -191,7 +199,6 @@ describe('Adkernel adapter', function () { cid: '16855' }] }], - cur: 'USD' }, usersyncOnlyResponse = { id: 'nobid1', ext: { @@ -221,21 +228,38 @@ describe('Adkernel adapter', function () { } }), adomain: ['displayurl.com'], + cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], cid: '1', - crid: '4' + crid: '4', + ext: { + 'advertiser_id': 777, + 'advertiser_name': 'advertiser', + 'agency_name': 'agency' + } }] }], bidid: 'pTuOlf5KHUo', - cur: 'USD' + cur: 'EUR' }; + var sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000, bidderCode: 'adkernel'}); } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { - let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => dnt); + 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)); @@ -307,6 +331,7 @@ describe('Adkernel adapter', function () { it('should fill device with caller macro', function () { expect(bidRequest).to.have.property('device'); expect(bidRequest.device).to.have.property('ip', 'caller'); + expect(bidRequest.device).to.have.property('ipv6', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); expect(bidRequest.device).to.have.property('dnt', 1); }); @@ -328,6 +353,14 @@ describe('Adkernel adapter', function () { expect(bidRequest.user.ext).to.be.eql({'consent': 'test-consent-string'}); }); + it('should contain coppa if configured', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_zone1]); + let bidRequest = bidRequests[0]; + expect(bidRequest).to.have.property('regs'); + expect(bidRequest.regs).to.have.property('coppa', 1); + }); + it('should\'t contain consent string if gdpr isn\'t applied', function () { let [_, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', {gdprConsent: {gdprApplies: false}})); let bidRequest = bidRequests[0]; @@ -342,9 +375,32 @@ describe('Adkernel adapter', function () { }); it('should forward default bidder timeout', function() { - let [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST); + let [_, bidRequests] = buildRequest([bid1_zone1]); expect(bidRequests[0]).to.have.property('tmax', 3000); }); + + it('should set bidfloor if configured', function() { + let bid = Object.assign({}, bid1_zone1); + bid.getFloor = function() { + return { + currency: 'USD', + floor: 0.145 + } + }; + 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 () { @@ -495,6 +551,7 @@ describe('Adkernel adapter', function () { expect(resp).to.have.property('ttl'); expect(resp).to.have.property('mediaType', BANNER); expect(resp).to.have.property('ad'); + expect(resp).to.have.property('dealId', 'deal'); expect(resp.ad).to.have.string(''); }); @@ -540,8 +597,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(6); - expect(spec.aliases).to.include.members(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon']); + expect(spec.aliases).to.be.an('array').that.is.not.empty; }); }); @@ -575,7 +631,13 @@ describe('Adkernel adapter', function () { let resp = spec.interpretResponse({body: nativeResponse}, pbRequests[0])[0]; expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp).to.have.property('cpm', 2.25); - expect(resp).to.have.property('currency', 'USD'); + expect(resp).to.have.property('currency', 'EUR'); + expect(resp).to.have.property('meta'); + 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(['displayurl.com']); + expect(resp.meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); expect(resp).to.have.property('mediaType', NATIVE); expect(resp).to.have.property('native'); expect(resp.native).to.have.property('clickUrl', 'http://rtb.com/click?i=pTuOlf5KHUo_0'); 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 new file mode 100644 index 00000000000..01ab3afcf2f --- /dev/null +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -0,0 +1,253 @@ +import adapterManager from 'src/adapterManager.js'; +import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/adlooxAnalyticsAdapter.js'; +import { AUCTION_COMPLETED } from 'src/auction.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import { EVENTS } from 'src/constants.json'; +import * as utils from 'src/utils.js'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +const analyticsAdapterName = 'adloox'; + +describe('Adloox Analytics Adapter', function () { + let sandbox; + + const esplode = 'esplode'; + + const bid = { + bidder: 'dummy', + adUnitCode: 'ad-slot-1', + mediaType: 'display' + }; + + const auctionDetails = { + auctionStatus: AUCTION_COMPLETED, + bidsReceived: [ + bid + ] + }; + + const analyticsOptions = { + js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', + client: 'adlooxtest', + clientid: 127, + platformid: 0, + tagid: 0, + params: { + dummy1: '%%client%%', + dummy2: '%%pbadslot%%', + dummy3: function(bid) { throw new Error(esplode) } + } + }; + + adapterManager.registerAnalyticsAdapter({ + code: analyticsAdapterName, + adapter: analyticsAdapter + }); + describe('enableAnalytics', function () { + describe('invalid options', function () { + it('should require options', function (done) { + adapterManager.enableAnalytics({ + provider: analyticsAdapterName + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + it('should reject non-string options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = function () { }; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + it('should reject non-function options.toselector', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.toselector = esplode; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + [ 'client', 'clientid', 'platformid', 'tagid' ].forEach(function (o) { + it('should require options.' + o, function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + delete analyticsOptionsLocal[o]; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + + it('should reject non-object options.params', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.params = esplode; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + }); + + describe('process', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: utils.deepClone(analyticsOptions) + }); + + expect(analyticsAdapter.context).is.not.null; + }); + + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + + sandbox.restore(); + sandbox = undefined; + }); + + describe('event', function () { + it('should preload the script on AUCTION_END only once', function (done) { + const insertElementStub = sandbox.stub(utils, 'insertElement'); + + const uri = utils.parseUrl(analyticsAdapter.url(analyticsOptions.js)); + const isLinkPreloadAsScript = function(arg) { + const href_uri = utils.parseUrl(arg.href); // IE11 requires normalisation (hostname always includes port) + return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; + }; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.callCount).to.equal(1); + + done(); + }); + + it('should inject verification JS on BID_WON', function (done) { + const url = analyticsAdapter.url(`${analyticsOptions.js}#`); + + const parent = document.createElement('div'); + + const slot = document.createElement('div'); + slot.id = bid.adUnitCode; + parent.appendChild(slot); + + const dummy = document.createElement('div'); + parent.appendChild(dummy); + + const querySelectorStub = sandbox.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); + + events.emit(EVENTS.BID_WON, bid); + + const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; + + expect(urlInserted.substr(0, url.length)).to.equal(url); + expect(moduleCode).to.equal(analyticsAdapterName); + expect(/[#&]creatype=2(&|$)/.test(urlInserted)).is.true; // prebid 'display' -> adloox '2' + expect(new RegExp('[#&]dummy3=' + encodeURIComponent('ERROR: ' + esplode) + '(&|$)').test(urlInserted)).is.true; + + 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(EVENTS.BID_WON, bidIgnore); + + expect(parent.querySelector('script')).is.null; + + done(); + }); + }); + + describe('command', function () { + it('should return config', function (done) { + const expected = utils.deepClone(analyticsOptions); + delete expected.js; + delete expected.toselector; + delete expected.params; + + analyticsCommand(COMMAND.CONFIG, null, function (response) { + expect(utils.deepEqual(response, expected)).is.true; + + done(); + }); + }); + + it('should return expanded URL', function (done) { + const data = { + url: 'https://example.com?', + args: [ + [ 'client', '%%client%%' ] + ], + bid: bid, + ids: true + }; + const expected = `${data.url}${data.args[0][0]}=${analyticsOptions.client}`; + + analyticsCommand(COMMAND.URL, data, function (response) { + expect(response.substr(0, expected.length)).is.equal(expected); + + done(); + }); + }); + + it('should inject tracking event', function (done) { + const data = { + eventType: EVENTS.BID_WON, + args: bid + }; + + analyticsCommand(COMMAND.TRACK, data, function (response) { + expect(response).is.undefined; + + done(); + }); + }); + }); + }); +}); 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..fd467f074ac 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,167 +103,77 @@ 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(); it('Returns valid URL and type', function () { 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 6d2e3059dc8..6dfde0d0652 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -1,9 +1,11 @@ import {expect} from 'chai'; import {spec} from 'modules/admixerBidAdapter.js'; 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.0.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'; describe('AdmixerAdapter', function () { @@ -57,6 +59,7 @@ describe('AdmixerAdapter', function () { } ]; let bidderRequest = { + bidderCode: BIDDER_CODE, refererInfo: { referer: 'https://example.com' } @@ -64,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]); }); @@ -72,39 +75,54 @@ 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 () { + config.setBidderConfig({ + bidders: [BIDDER_CODE], // one or more bidders + config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + }); + const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); + expect(request.method).to.equal('POST'); }); }); describe('interpretResponse', function () { let response = { - body: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'bidId': '5e4e763b6bc60b' - }] + body: { + ads: [{ + 'currency': 'USD', + 'cpm': 6.210000, + 'ad': '
ad
', + 'width': 300, + 'height': 600, + 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', + 'ttl': 360, + 'netRevenue': false, + 'requestId': '5e4e763b6bc60b', + 'dealId': 'asd123', + 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} + }] + } }; it('should get correct bid response', function () { - const body = response.body; + const ads = response.body.ads; let expectedResponse = [ { - 'requestId': body[0].bidId, - 'cpm': body[0].cpm, - 'creativeId': body[0].creativeId, - 'width': body[0].width, - 'height': body[0].height, - 'ad': body[0].ad, - 'vastUrl': undefined, - 'currency': body[0].currency, - 'netRevenue': body[0].netRevenue, - 'ttl': body[0].ttl, + 'requestId': ads[0].requestId, + 'cpm': ads[0].cpm, + 'creativeId': ads[0].creativeId, + 'width': ads[0].width, + 'height': ads[0].height, + 'ad': ads[0].ad, + 'currency': ads[0].currency, + 'netRevenue': ads[0].netRevenue, + 'ttl': ads[0].ttl, + 'dealId': ads[0].dealId, + 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} } ]; @@ -119,4 +137,34 @@ describe('AdmixerAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs', function () { + let imgUrl = 'https://example.com/img1'; + let frmUrl = 'https://example.com/frm2'; + let responses = [{ + body: { + cm: { + pixels: [ + imgUrl + ], + iframes: [ + frmUrl + ], + } + } + }]; + + it('Returns valid values', function () { + let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); + let userSyncImg = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: false}, responses); + let userSyncFrm = spec.getUserSyncs({pixelEnabled: false, iframeEnabled: true}, responses); + expect(userSyncAll).to.be.an('array').with.lengthOf(2); + expect(userSyncImg).to.be.an('array').with.lengthOf(1); + expect(userSyncImg[0].url).to.be.equal(imgUrl); + expect(userSyncImg[0].type).to.be.equal('image'); + expect(userSyncFrm).to.be.an('array').with.lengthOf(1); + expect(userSyncFrm[0].url).to.be.equal(frmUrl); + expect(userSyncFrm[0].type).to.be.equal('iframe'); + }); + }); }); diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js new file mode 100644 index 00000000000..18107b780db --- /dev/null +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -0,0 +1,81 @@ +import {admixerIdSubmodule} from 'modules/admixerIdSystem.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 pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; +const getIdParams = {params: {pid: pid}}; +describe('admixerId tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if pid configParam was not passed when getId', function () { + admixerIdSubmodule.getId(); + expect(logErrorStub.callCount).to.be.equal(1); + + admixerIdSubmodule.getId({}); + expect(logErrorStub.callCount).to.be.equal(2); + + admixerIdSubmodule.getId({params: {}}); + expect(logErrorStub.callCount).to.be.equal(3); + + admixerIdSubmodule.getId({params: {pid: 123}}); + expect(logErrorStub.callCount).to.be.equal(4); + }); + + it('should NOT call the admixer id endpoint if gdpr applies but consent string is missing', function () { + let submoduleCallback = admixerIdSubmodule.getId(getIdParams, { gdprApplies: true }); + expect(submoduleCallback).to.be.undefined; + }); + + it('should call the admixer id endpoint', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call callback with user id', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({setData: {visitorid: '571058d70bce453b80e6d98b4f8a81e3'}}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('571058d70bce453b80e6d98b4f8a81e3'); + }); + + it('should continue to callback if ajax response 204', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 204 + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); +}); diff --git a/test/spec/modules/adnowBidAdapter_spec.js b/test/spec/modules/adnowBidAdapter_spec.js new file mode 100644 index 00000000000..a8013e3fa04 --- /dev/null +++ b/test/spec/modules/adnowBidAdapter_spec.js @@ -0,0 +1,163 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adnowBidAdapter.js'; + +describe('adnowBidAdapter', function () { + describe('isBidRequestValid', function () { + it('Should return true', function() { + expect(spec.isBidRequestValid({ + bidder: 'adnow', + params: { + codeId: 12345 + } + })).to.equal(true); + }); + + it('Should return false when required params is not passed', function() { + expect(spec.isBidRequestValid({ + bidder: 'adnow', + params: {} + })).to.equal(false); + }); + }); + + describe('buildRequests', function() { + it('Common settings', function() { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData) + .to.match(/Id=12345/) + .to.match(/mediaType=native/) + .to.match(/out=prebid/) + .to.match(/requestid=bid12345/) + .to.match(/d_user_agent=.+/); + }); + + it('Banner sizes', function () { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData).to.match(/sizes=300x250/); + }); + + it('Native sizes', function () { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + }, + mediaTypes: { + native: { + image: { + sizes: [100, 100] + } + } + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData) + .to.match(/width=100/) + .to.match(/height=100/); + }); + }); + + describe('interpretResponse', function() { + const request = { + bidRequest: { + bidId: 'bid12345' + } + }; + + it('Response with native bid', function() { + const response = { + currency: 'USD', + cpm: 0.5, + native: { + title: 'Title', + body: 'Body', + sponsoredBy: 'AdNow', + clickUrl: '//click.url', + image: { + url: '//img.url', + height: 200, + width: 200 + } + }, + meta: { + mediaType: 'native' + } + }; + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.have.keys('requestId', 'cpm', 'currency', 'native', 'creativeId', 'netRevenue', 'meta', 'ttl'); + + const nativePart = bid.native; + + expect(nativePart.title).to.be.equal('Title'); + expect(nativePart.body).to.be.equal('Body'); + expect(nativePart.clickUrl).to.be.equal('//click.url'); + expect(nativePart.image.url).to.be.equal('//img.url'); + expect(nativePart.image.height).to.be.equal(200); + expect(nativePart.image.width).to.be.equal(200); + }); + + it('Response with banner bid', function() { + const response = { + currency: 'USD', + cpm: 0.5, + ad: '
Banner
', + meta: { + mediaType: 'banner' + } + }; + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.have.keys( + 'requestId', 'cpm', 'currency', 'ad', 'creativeId', 'netRevenue', 'meta', 'ttl', 'width', 'height' + ); + + expect(bid.ad).to.be.equal('
Banner
'); + }); + + it('Response with no bid should return an empty array', function() { + const noBidResponses = [ + false, + {}, + {body: false}, + {body: {}} + ]; + + noBidResponses.forEach(response => { + return expect(spec.interpretResponse(response, request)).to.be.an('array').that.is.empty; + }); + }); + }); +}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 54ff038c083..e2682b8d0d5 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -2,27 +2,53 @@ 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 ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + 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 = `${URL}${tzo}&format=json&userId=${usi}`; + 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 = [ { + bidId: '123', bidder: 'adnuntius', params: { auId: '8b6bc', network: 'adnuntius', }, - bidId: '123' + } - ]; + ] + + const singleBidRequest = { + bid: [ + { + bidId: '123', + } + ] + } const serverResponse = { body: { 'adUnits': [ { 'auId': '000000000008b6bc', - 'targetId': '', + 'targetId': '123', 'html': '

hi!

', 'matchedAdCount': 1, 'responseId': 'adn-rsp-1460129238', @@ -67,8 +93,15 @@ describe('adnuntiusBidAdapter', function () { 'lineItemId': 'scyjdyv3mzgdsnpf', 'layoutId': 'sw6gtws2rdj1kwby', 'layoutName': 'Responsive image' - } + }, + ] + }, + { + 'auId': '000000000008b6bc', + 'targetId': '456', + 'matchedAdCount': 0, + 'responseId': 'adn-rsp-1460129238', } ] } @@ -96,22 +129,92 @@ 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\"}]}'); + 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); }); }); describe('interpretResponse', function () { it('should return valid response when passed valid server response', function () { - const request = spec.buildRequests(bidRequests); - const interpretedResponse = spec.interpretResponse(serverResponse, request[0]); + const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); const ad = serverResponse.body.adUnits[0].ads[0] expect(interpretedResponse).to.have.lengthOf(1); expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.cpm.currency); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].html); expect(interpretedResponse[0].ttl).to.equal(360); }); 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/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index 594fc4ac7b7..40605b17b20 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -25,19 +25,16 @@ describe('Adot Adapter', function () { bidder: 'adot', bidderRequestId: 'bid_request_id', bidId: 'bid_id', - params: { + params: {}, + mediaTypes: { video: { + context: 'outstream', + playerSize: [[300, 250]], mimes: ['video/mp4'], minDuration: 5, maxDuration: 30, protocols: [2, 3] } - }, - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[300, 250]] - } } }, @@ -48,17 +45,17 @@ describe('Adot Adapter', function () { bidId: 'bid_id', params: { video: { - instreamContext: 'pre-roll', - mimes: ['video/mp4'], - minDuration: 5, - maxDuration: 30, - protocols: [2, 3] + instreamContext: 'pre-roll' } }, mediaTypes: { video: { context: 'instream', - playerSize: [[300, 250]] + playerSize: [[300, 250]], + mimes: ['video/mp4'], + minDuration: 5, + maxDuration: 30, + protocols: [2, 3] } } }, @@ -557,6 +554,7 @@ describe('Adot Adapter', function () { price: 1.5, h: 350, w: 300, + adomain: ['adot'], ext: { adot: { media_type: 'banner' @@ -568,6 +566,7 @@ describe('Adot Adapter', function () { crid: 'creative_id_2', adm: 'creative_data_2_${AUCTION_PRICE}', nurl: 'win_notice_url_2_${AUCTION_PRICE}', + adomain: ['adot'], price: 2.5, h: 400, w: 350, @@ -913,14 +912,14 @@ describe('Adot Adapter', function () { 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; + adUnit.mediaTypes.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; + adUnit.mediaTypes.video.maxDuration = undefined; expect(spec.isBidRequestValid(adUnit)).to.equal(true); }); @@ -941,84 +940,84 @@ describe('Adot Adapter', function () { 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; + adUnit.mediaTypes.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'; + adUnit.mediaTypes.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; + adUnit.mediaTypes.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'; + adUnit.mediaTypes.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 = []; + adUnit.mediaTypes.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]; + adUnit.mediaTypes.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'; + adUnit.mediaTypes.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'; + adUnit.mediaTypes.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; + adUnit.mediaTypes.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'; + adUnit.mediaTypes.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 = []; + adUnit.mediaTypes.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']; + adUnit.mediaTypes.video.protocols = ['bad_protocols_value']; expect(spec.isBidRequestValid(adUnit)).to.equal(false); }); @@ -1452,13 +1451,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid pre-roll instream ad unit', function () { @@ -1476,13 +1475,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid mid-roll instream ad unit', function () { @@ -1500,13 +1499,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid post-roll instream ad unit', function () { @@ -1524,13 +1523,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid ad unit without player size', function () { @@ -1548,13 +1547,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid ad unit with an empty player size', function () { @@ -1572,13 +1571,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with one impression when given a valid ad unit with multiple player sizes', function () { @@ -1596,18 +1595,18 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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; + adUnit.mediaTypes.video.minDuration = undefined; const adUnits = [adUnit]; @@ -1620,18 +1619,18 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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; + adUnit.mediaTypes.video.maxDuration = undefined; const adUnits = [adUnit]; @@ -1644,13 +1643,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.video.protocols); }); it('should return a server request with two impressions when given two valid ad units with different impression identifiers', function () { @@ -1671,31 +1670,31 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[1].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[1].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[1].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].mediaTypes.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; + adUnit_1.mediaTypes.video.minDuration = 10; const adUnit_2 = utils.deepClone(examples.adUnit_video_outstream); - adUnit_2.params.video.minDuration = 15; + adUnit_2.mediaTypes.video.minDuration = 15; const adUnits = [adUnit_1, adUnit_2]; @@ -1708,13 +1707,13 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[0].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].mediaTypes.video.protocols); }); it('should return two server requests with one impression when given two valid ad units with different bid request identifiers', function () { @@ -1735,25 +1734,25 @@ describe('Adot Adapter', function () { 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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.minDuration); + expect(serverRequests[0].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[0].mediaTypes.video.maxDuration); + expect(serverRequests[0].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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.mimes).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[0].mediaTypes.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); + expect(serverRequests[1].data.imp[0].video.minduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.minDuration); + expect(serverRequests[1].data.imp[0].video.maxduration).to.exist.and.to.be.a('number').and.to.equal(adUnits[1].mediaTypes.video.maxDuration); + expect(serverRequests[1].data.imp[0].video.protocols).to.exist.and.to.be.an('array').and.to.deep.equal(adUnits[1].mediaTypes.video.protocols); }); }); @@ -2062,10 +2061,11 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2087,13 +2087,57 @@ describe('Adot Adapter', function () { 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 two ads when given a valid server response with two bids that contains adomain', 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(serverResponse.body.seatbid[0].bid[0].adm); + 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].meta.advertiserDomains[0]).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].adomain[0]) 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); @@ -2104,7 +2148,8 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[1].adm); + expect(ads[1].meta.advertiserDomains[0]).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].adomain[0]) + 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); @@ -2592,10 +2637,11 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2617,10 +2663,11 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2642,11 +2689,12 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].nurl); + 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); @@ -2721,12 +2769,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2745,12 +2794,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2769,12 +2819,14 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2786,9 +2838,9 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[1].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2808,12 +2860,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2833,13 +2886,14 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].nurl); + 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(serverResponse.body.seatbid[0].bid[0].nurl); + 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); @@ -2858,12 +2912,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2883,12 +2938,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2909,12 +2965,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2934,12 +2991,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -2959,12 +3017,13 @@ describe('Adot Adapter', function () { 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(serverResponse.body.seatbid[0].bid[0].adm); + 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(serverResponse.body.seatbid[0].bid[0].adm); + 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); @@ -3066,20 +3125,6 @@ describe('Adot Adapter', function () { 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); - }); }); }); 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/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js index 737f1c284e1..92fd672df47 100644 --- a/test/spec/modules/adponeBidAdapter_spec.js +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -110,122 +110,109 @@ describe('adponeBidAdapter', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); -}); - -describe('interpretResponse', function () { - let serverResponse; - let bidRequest = { data: {id: '1234'} }; - - beforeEach(function () { - serverResponse = { - body: { - id: '2579e20c0bb89', - seatbid: [ - { - bid: [ - { - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - impid: '2579e20c0bb89_0', - price: 1, - adm: '', - adomain: [ - 'www.addomain.com' - ], - iurl: 'https://localhost11', - crid: 'creative111', - h: 250, - w: 300, - ext: { - dspid: 6 + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: {id: '1234'} }; + + beforeEach(function () { + serverResponse = { + body: { + id: '2579e20c0bb89', + seatbid: [ + { + bid: [ + { + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + impid: '2579e20c0bb89_0', + price: 1, + adm: '', + meta: { + adomain: [ + 'adpone.com' + ] + }, + iurl: 'https://localhost11', + crid: 'creative111', + h: 250, + w: 300, + ext: { + dspid: 6 + } } - } - ], - seat: 'adpone' - } - ], - cur: 'USD' - }, - }; - }); - - it('validate_response_params', function() { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); - expect(newResponse[0].requestId).to.be.equal('1234'); - expect(newResponse[0].cpm).to.be.equal(1); - expect(newResponse[0].width).to.be.equal(300); - expect(newResponse[0].height).to.be.equal(250); - expect(newResponse[0].currency).to.be.equal('USD'); - expect(newResponse[0].netRevenue).to.be.equal(true); - expect(newResponse[0].ttl).to.be.equal(300); - expect(newResponse[0].ad).to.be.equal(''); - }); + ], + seat: 'adpone' + } + ], + cur: 'USD' + }, + }; + }); - it('should correctly reorder the server response', function () { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse.length).to.be.equal(1); - expect(newResponse[0]).to.deep.equal({ - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - requestId: '1234', - cpm: 1, - width: 300, - height: 250, - creativeId: 'creative111', - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: '' + it('validate_response_params', function() { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); + expect(newResponse[0].requestId).to.be.equal('1234'); + expect(newResponse[0].cpm).to.be.equal(1); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].netRevenue).to.be.equal(true); + expect(newResponse[0].ttl).to.be.equal(300); + expect(newResponse[0].ad).to.be.equal(''); }); - }); - it('should not add responses if the cpm is 0 or null', function () { - serverResponse.body.seatbid[0].bid[0].price = 0; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]); + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + meta: { + advertiserDomains: [ + 'adpone.com' + ] + }, + requestId: '1234', + cpm: 1, + width: 300, + height: 250, + creativeId: 'creative111', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '' + }); + }); - serverResponse.body.seatbid[0].bid[0].price = null; - response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]) - }); - it('should add responses if the cpm is valid', function () { - serverResponse.body.seatbid[0].bid[0].price = 0.5; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.not.deep.equal([]); - }); -}); + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); -describe('getUserSyncs', function () { - it('Verifies that getUserSyncs is a function', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - }); - it('Verifies getUserSyncs returns expected result', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - expect(spec.getUserSyncs({iframeEnabled: true})).to.deep.equal({ - type: 'iframe', - url: 'https://eu-ads.adpone.com' + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]) + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); }); }); - it('Verifies that iframeEnabled: false returns an empty array', function () { - expect(spec.getUserSyncs({iframeEnabled: false})).to.deep.equal(EMPTY_ARRAY); - }); - it('Verifies that iframeEnabled: null returns an empty array', function () { - expect(spec.getUserSyncs(null)).to.deep.equal(EMPTY_ARRAY); - }); -}); -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('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); + }); }); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 3508a1175a6..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', '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]); }); @@ -106,6 +109,23 @@ describe('AdprimebBidAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + describe('buildRequests with user ids', function () { + bid.userId = {} + bid.userId.idl_env = 'idl_env123'; + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Return bids with user identeties', 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('identeties') + expect(placement.identeties).to.be.an('object') + expect(placement.identeties).to.have.property('identityLink') + expect(placement.identeties.identityLink).to.be.equal('idl_env123') + } + }); + }); describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -120,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); @@ -137,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 = { @@ -149,7 +171,8 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: {} }] }; let videoResponses = spec.interpretResponse(video); @@ -157,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'); @@ -165,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 = { @@ -182,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); @@ -201,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 = { @@ -267,4 +293,14 @@ describe('AdprimebBidAdapter', function () { 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://delta.adprime.com'); + }); + }); }); diff --git a/test/spec/modules/1ad4goodBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js similarity index 62% rename from test/spec/modules/1ad4goodBidAdapter_spec.js rename to test/spec/modules/adrelevantisBidAdapter_spec.js index b9cd86a4cf7..b87f9d6b86c 100644 --- a/test/spec/modules/1ad4goodBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -1,13 +1,13 @@ import { expect } from 'chai'; -import { spec } from 'modules/1ad4goodBidAdapter.js'; +import { spec } from 'modules/adrelevantisBidAdapter.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'; +const ENDPOINT = 'https://ssp.adrelevantis.com/prebid'; -describe('AdforgoodAdapter', function () { +describe('AdrelevantisAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { @@ -18,7 +18,7 @@ describe('AdforgoodAdapter', function () { describe('isBidRequestValid', function () { let bid = { - 'bidder': '1ad4good', + 'bidder': 'adrelevantis', 'params': { 'placementId': '10433394' }, @@ -46,7 +46,7 @@ describe('AdforgoodAdapter', function () { describe('buildRequests', function () { let bidRequests = [ { - 'bidder': '1ad4good', + 'bidder': 'adrelevantis', 'params': { 'placementId': '10433394' }, @@ -87,7 +87,7 @@ describe('AdforgoodAdapter', function () { }); it('should populate the ad_types array on all requests', function () { - ['banner', 'video'].forEach(type => { + ['banner', 'video', 'native'].forEach(type => { const bidRequest = Object.assign({}, bidRequests[0]); bidRequest.mediaTypes = {}; bidRequest.mediaTypes[type] = {}; @@ -204,29 +204,117 @@ describe('AdforgoodAdapter', function () { }); }); - // 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]]; + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 10433394 + } + } + ); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); - // 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}]); + it('adds context data (category and keywords) to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('ortb2') + .returns({ + site: { + keywords: 'US Open', + category: 'sports/tennis' + } + }); - // delete bidRequest.sizes; + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - // request = spec.buildRequests([bidRequest]); - // payload = JSON.parse(request.data); + expect(payload.fpd.keywords).to.equal('US Open'); + expect(payload.fpd.category).to.equal('sports/tennis'); - // expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); - // }); + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); + }); + + 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({}, @@ -293,7 +381,7 @@ describe('AdforgoodAdapter', function () { it('should add gdpr consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { - 'bidderCode': '1ad4good', + 'bidderCode': 'adrelevantis', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, @@ -381,6 +469,20 @@ describe('AdforgoodAdapter', function () { rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') }); }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); }) describe('interpretResponse', function () { @@ -455,7 +557,7 @@ describe('AdforgoodAdapter', function () { 'ttl': 300, 'netRevenue': true, 'adUnitCode': 'code', - 'ads4good': { + 'adrelevantis': { 'buyerMemberId': 958 } } @@ -486,7 +588,7 @@ describe('AdforgoodAdapter', function () { expect(result.length).to.equal(0); }); - it('handles non-banner media responses', function () { + it('handles outstream video responses', function () { let response = { 'tags': [{ 'uuid': '84ab500420319d', @@ -506,7 +608,47 @@ describe('AdforgoodAdapter', function () { let bidderRequest = { bids: [{ bidId: '84ab500420319d', - adUnitCode: 'code' + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } }] } @@ -516,6 +658,83 @@ describe('AdforgoodAdapter', function () { expect(result[0]).to.have.property('mediaType', 'video'); }); + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.cta).to.equal('Do it'); + expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + }); + + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + it('should add deal_priority and deal_code', function() { let responseWithDeal = deepClone(response); responseWithDeal.tags[0].ads[0].deal_priority = 'high'; @@ -528,7 +747,7 @@ describe('AdforgoodAdapter', function () { }] } let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); - expect(Object.keys(result[0].ads4good)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(Object.keys(result[0].adrelevantis)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); }); it('should add advertiser id', function() { diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js new file mode 100644 index 00000000000..9d410090885 --- /dev/null +++ b/test/spec/modules/adriverBidAdapter_spec.js @@ -0,0 +1,584 @@ +import { expect } from 'chai'; +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 () { + 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: 'adriver', + params: { + placementId: '55:test_placement', + siteid: 'testSiteID' + }, + 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 () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let getAdUnitsStub; + const floor = 3; + + const 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' + } + } + ] + } + ] + } + ]; + + 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; + }; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should exist currency', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + 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); + + expect(payload.at).to.exist; + expect(payload.at).to.deep.equal(1); + }); + + it('should parse imp', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.imp[0]).to.exist; + expect(payload.imp[0].id).to.deep.equal('55:test_placement'); + + expect(payload.imp[0].ext).to.exist; + expect(payload.imp[0].ext.query).to.deep.equal('bn=15&custom=111=' + '30b31c1838de1e'); + + expect(payload.imp[0].banner).to.exist; + expect(payload.imp[0].banner.w).to.deep.equal(300); + expect(payload.imp[0].banner.h).to.deep.equal(250); + }); + + it('should parse pmp', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].pmp).to.exist; + + expect(payload.imp[0].pmp.deals).to.exist; + + expect(payload.imp[0].pmp.deals[0].bidfloor).to.exist; + expect(payload.imp[0].pmp.deals[0].bidfloor).to.deep.equal(3); + + expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.exist; + expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.deep.equal('RUB'); + }); + + 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'); + }); + }); + + describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + let response = { + 'id': '221594457-1615288400-1-46-', + 'bidid': 'D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA', + 'seatbid': [{ + 'bid': [{ + 'id': '1', + 'impid': '/19968336/header-bid-tag-0', + 'price': 4.29, + 'h': 250, + 'w': 300, + 'adid': '7121351', + 'adomain': ['http://ikea.com'], + 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA&bid=7121351&wprc=4.29&tuid=-1&custom=207=/19968336/header-bid-tag-0', + 'cid': '717570', + 'ext': '2c262a7058758d' + }] + }, { + 'bid': [{ + 'id': '1', + 'impid': '/19968336/header-bid-tag-0', + 'price': 17.67, + 'h': 600, + 'w': 300, + 'adid': '7121369', + 'adomain': ['http://ikea.com'], + 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=DdtToXX5cpTaMMxrJSEsOsUIXt3WmC3jOvuNI5DguDrY8edFG60Jg1M-iMkVNKQ4OiAdHSLPJLQQXMUXZfI9VbjMoGCb-zzOTPiMpshI&bid=7121369&wprc=17.67&tuid=-1&custom=207=/19968336/header-bid-tag-0', + 'cid': '717570', + 'ext': '2c262a7058758d' + }] + }], + 'cur': 'RUB' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '2c262a7058758d', + cpm: 4.29, + width: 300, + height: 250, + creativeId: '/19968336/header-bid-tag-0', + currency: 'RUB', + netRevenue: true, + ttl: 3000, + meta: { + advertiserDomains: ['http://ikea.com'] + }, + ad: '' + } + ]; + 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); + }); + }); + + describe('function _getFloor', 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' + } + } + ] + } + ] + } + ]; + + const floorTestData = { + 'currency': 'RUB', + 'floor': 1.50 + }; + + const bitRequestStandard = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestStandard[0].getFloor = () => { + return floorTestData; + }; + + it('valid BidRequests', function () { + const request = spec.buildRequests(bitRequestStandard); + const payload = JSON.parse(request.data); + + expect(typeof bitRequestStandard[0].getFloor).to.equal('function'); + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestEmptyCurrency = JSON.parse(JSON.stringify(bidRequests)); + + const floorTestDataEmptyCurrency = { + 'currency': 'RUB', + 'floor': 1.50 + }; + + bitRequestEmptyCurrency[0].getFloor = () => { + return floorTestDataEmptyCurrency; + }; + + it('empty currency', function () { + const request = spec.buildRequests(bitRequestEmptyCurrency); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(1.50); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestFloorNull = JSON.parse(JSON.stringify(bidRequests)); + + const floorTestDataFloorNull = { + 'currency': '', + 'floor': null + }; + + bitRequestFloorNull[0].getFloor = () => { + return floorTestDataFloorNull; + }; + + it('empty floor', function () { + const request = spec.buildRequests(bitRequestFloorNull); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(0); + }); + + const bitRequestGetFloorNotFunction = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestGetFloorNotFunction[0].getFloor = 0; + + it('bid.getFloor is not a function', function () { + const request = spec.buildRequests(bitRequestGetFloorNotFunction); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(0); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + }); + + const bitRequestGetFloorBySized = JSON.parse(JSON.stringify(bidRequests)); + + bitRequestGetFloorBySized[0].getFloor = (requestParams = {currency: 'USD', mediaType: '*', size: '*'}) => { + if (requestParams.size.length === 2 && requestParams.size[0] === 300 && requestParams.size[1] === 250) { + return { + 'currency': 'RUB', + 'floor': 3.33 + } + } else { + return {} + } + }; + + it('bid.getFloor get size', function () { + const request = spec.buildRequests(bitRequestGetFloorBySized); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.equal(3.33); + expect(payload.imp[0].bidfloorcur).to.equal('RUB'); + 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 9c694668703..4cfb367efb3 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/adtelligentBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/', @@ -9,7 +10,16 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb2.adtelligent.com/v2/auction/', 'https://ghb.adtelligent.com/v2/auction/' ]; +const aliasEP = { + appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', + appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', + onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', + mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/', + navelix: 'https://ghb.hb.navelix.com/v2/auction/', + bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', +}; +const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; const DISPLAY_REQUEST = { 'bidder': 'adtelligent', 'params': { @@ -70,15 +80,16 @@ 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 = { 'source': { 'aid': 12345, 'pubId': 54321 }, 'bids': [{ 'ad': '', + 'adUrl': 'adUrl', 'requestId': '2e41f65424c87c', 'creative_id': 342516, 'cmpId': 342516, @@ -152,7 +163,10 @@ const videoEqResponse = [{ height: 480, width: 640, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: ['a.com'] + } }]; const displayEqResponse = [{ @@ -162,10 +176,15 @@ const displayEqResponse = [{ netRevenue: true, currency: 'USD', ad: '', + adUrl: 'adUrl', height: 250, width: 300, ttl: 300, - cpm: 0.9 + cpm: 0.9, + meta: { + advertiserDomains: [] + } + }]; describe('adtelligentBidAdapter', () => { @@ -241,15 +260,24 @@ describe('adtelligentBidAdapter', () => { let videoBidRequests = [VIDEO_REQUEST]; let displayBidRequests = [DISPLAY_REQUEST]; let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; - const displayRequest = spec.buildRequests(displayBidRequests, {}); - const videoRequest = spec.buildRequests(videoBidRequests, {}); - const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, {}); - const rotatingRequest = spec.buildRequests(displayBidRequests, {}); + const displayRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); + const videoRequest = spec.buildRequests(videoBidRequests, DEFAULT_ADATPER_REQ); + const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, DEFAULT_ADATPER_REQ); + const rotatingRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); it('rotates endpoints', () => { const bidReqUrls = [displayRequest[0], videoRequest[0], videoAndDisplayRequests[0], rotatingRequest[0]].map(br => br.url); expect(bidReqUrls).to.deep.equal(EXPECTED_ENDPOINTS); }) + it('makes correct host for aliases', () => { + for (const alias in aliasEP) { + const bidReq = deepClone(DISPLAY_REQUEST) + bidReq.bidder = alias; + const [bidderRequest] = spec.buildRequests([bidReq], { bidderCode: alias }); + expect(bidderRequest.url).to.equal(aliasEP[alias]); + } + }) + it('building requests as arrays', () => { expect(videoRequest).to.be.a('array'); expect(displayRequest).to.be.a('array'); @@ -264,7 +292,7 @@ describe('adtelligentBidAdapter', () => { expect(videoAndDisplayRequests.every(comparator)).to.be.true; }); it('forms correct ADPOD request', () => { - const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], {})[0].data; + 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'); @@ -277,7 +305,8 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'video', Aid: 12345, - Sizes: '480x360,640x480' + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' }; expect(data.BidRequests[0]).to.deep.equal(eq); }); @@ -289,7 +318,8 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'display', Aid: 12345, - Sizes: '300x250' + Sizes: '300x250', + PlacementId: 'adunit-code' }; expect(data.BidRequests[0]).to.deep.equal(eq); @@ -301,12 +331,14 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'display', Aid: 12345, - Sizes: '300x250' + Sizes: '300x250', + PlacementId: 'adunit-code' }, { CallbackId: '84ab500420319d', AdType: 'video', Aid: 12345, - Sizes: '480x360,640x480' + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' }] expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); 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 new file mode 100644 index 00000000000..b499d077a3c --- /dev/null +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -0,0 +1,431 @@ +import {expect} from 'chai' +import {spec} from 'modules/adtrueBidAdapter.js' +import {newBidder} from 'src/adapters/bidderFactory.js' +import * as utils from '../../../src/utils.js'; +import {config} from 'src/config.js'; + +describe('AdTrueBidAdapter', function () { + const adapter = newBidder(spec) + let bidRequests; + let bidResponses; + let bidResponses2; + beforeEach(function () { + bidRequests = [ + { + bidder: 'adtrue', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + publisherId: '1212', + zoneId: '21423', + reserve: 0 + }, + placementCode: 'adunit-code-1', + sizes: [[300, 250]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + } + } + ]; + bidResponses = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue' + } + ] + } + } + }; + bidResponses2 = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 2, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image' + }, + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus' + } + ] + } + } + }; + }); + + describe('.code', function () { + it('should return a bidder code of adtrue', function () { + expect(spec.code).to.equal('adtrue') + }) + }) + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423', + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case: publisherId not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('valid bid case: zoneId is not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('should return false if there are no params', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no publisherId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no zoneId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return true if the bid is valid', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('Request formation', function () { + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('Endpoint/method checking', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(request.url).to.equal('https://hb.adtrue.com/prebid/auction'); + expect(request.method).to.equal('POST'); + }); + + it('test flag not sent when adtrueTest=true is absent in page url', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.test).to.equal(undefined); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.reserve); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with GDPR Consent', function () { + let bidRequest = { + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.user.ext.consent).to.equal('kjfdniwjnifwenrif3'); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with USP/CCPA Consent', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('should NOT include coppa flag in bid request if coppa config is not present', () => { + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + }); + it('should include coppa flag in bid request if coppa is set to true', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.regs.coppa).to.equal(1); + sandbox.restore(); + }); + it('should NOT include coppa flag in bid request if coppa is set to false', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + sandbox.restore(); + }); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].currency).to.equal('USD'); + expect(response[0].ttl).to.equal(300); + expect(response[0].meta.clickUrl).to.equal('adtrue.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('adtrue.com'); + expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + }); + }); + describe('getUserSyncs', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses], undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + // Multiple user sync output + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses2], undefined, undefined)).to.deep.equal([ + { + type: 'image', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }, + { + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + } + ]); + }); + }); +}); 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 306914960c3..551d50b60e7 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} 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 = { @@ -29,15 +30,15 @@ describe('AdxcgAdapter', function () { adzoneid: '20', video: { api: [2], - protocols: [1, 2], - mimes: ['video/mp4'], maxduration: 30 } }, mediaTypes: { video: { context: 'instream', - playerSize: [[640, 480]] + playerSize: [[640, 480]], + protocols: [1, 2], + mimes: ['video/mp4'], } }, adUnitCode: 'adunit-code', @@ -91,7 +92,7 @@ describe('AdxcgAdapter', function () { expect(spec.isBidRequestValid(bidBanner)).to.equal(true); }); - it('should return true when required params not found', function () { + it('should return false when required params not found', function () { expect(spec.isBidRequestValid({})).to.be.false; }); @@ -106,10 +107,6 @@ describe('AdxcgAdapter', function () { const simpleVideo = JSON.parse(JSON.stringify(bidVideo)); simpleVideo.params.adzoneid = 123; expect(spec.isBidRequestValid(simpleVideo)).to.be.false; - simpleVideo.params.mimes = [1, 2, 3]; - expect(spec.isBidRequestValid(simpleVideo)).to.be.false; - simpleVideo.params.mimes = 'bad type'; - expect(spec.isBidRequestValid(simpleVideo)).to.be.false; }); }); @@ -124,7 +121,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + 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'); @@ -158,7 +155,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; // general part expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + 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'); @@ -196,7 +193,7 @@ describe('AdxcgAdapter', function () { let query = parsedRequestUrl.search; expect(query.renderformat).to.equal('javascript'); - expect(query.ver).to.equal('r20191128PB30'); + 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'); @@ -281,7 +278,7 @@ describe('AdxcgAdapter', function () { let bid = deepClone([bidBanner]); let bidderRequests = {}; - bid[0].userId = {id5id: 'id5idsample'}; + bid[0].userId = {id5id: {uid: 'id5idsample'}}; it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); @@ -327,123 +324,164 @@ describe('AdxcgAdapter', function () { }; let BANNER_RESPONSE = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 300, - height: 250, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - ad: '' - } - ], - header: {someheader: 'fakedata'} + 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' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; let BANNER_RESPONSE_WITHDEALID = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 300, - height: 250, - deal_id: '7722', - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - ad: '' - } - ], - header: {someheader: 'fakedata'} + 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' + } + } + ] + }], + cur: 'USD' + } }; let VIDEO_RESPONSE = { - body: [ - { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 640, - height: 360, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - vastUrl: 'vastContentUrl' - } - ], - header: {someheader: 'fakedata'} + 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' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; - let NATIVE_RESPONSE = { - body: [ + let NATIVE_RESPONSEob = { + assets: [ { - bidId: '84ab500420319d', - bidderCode: 'adxcg', - width: 0, - height: 0, - creativeId: '42', - cpm: 0.45, - currency: 'USD', - netRevenue: true, - nativeResponse: { - 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'] + 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 } } ], - header: {someheader: 'fakedata'} + link: { + url: 'linkContent' + }, + imptrackers: ['impressionTracker1', 'impressionTracker2'] + } + + 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' + } + } + ] + }], + cur: 'USD' + }, + headers: {someheader: 'fakedata'} }; 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); @@ -452,26 +490,30 @@ describe('AdxcgAdapter', function () { 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); 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']); }); it('handles regular responses with dealid', function () { - let result = spec.interpretResponse( - BANNER_RESPONSE_WITHDEALID, - BIDDER_REQUEST - ); + let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID); expect(result).to.have.lengthOf(1); 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.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); @@ -479,7 +521,7 @@ describe('AdxcgAdapter', function () { }); it('handles video responses', function () { - let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + let result = spec.interpretResponse(VIDEO_RESPONSE); expect(result).to.have.lengthOf(1); expect(result[0].width).to.equal(640); @@ -494,17 +536,19 @@ describe('AdxcgAdapter', function () { }); it('handles native responses', function () { - let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + let result = spec.interpretResponse(NATIVE_RESPONSE); expect(result[0].width).to.equal(0); expect(result[0].height).to.equal(0); - expect(result[0].mediaType).to.equal('native'); + 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); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].native.clickUrl).to.equal('linkContent'); expect(result[0].native.impressionTrackers).to.deep.equal([ 'impressionTracker1', @@ -545,4 +589,65 @@ describe('AdxcgAdapter', function () { ); }); }); + + describe('on bidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + it('should replace burl for banner', function () { + const burl = 'burl=${' + 'AUCTION_PRICE}'; + 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'}] + }; + spec.onBidWon(bid); + expect(bid.burl).to.deep.equal(burl); + }); + }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index d2d4e10c17f..befc95e5f24 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -58,12 +58,148 @@ describe('Adyoulike Adapter', function () { 'mediaTypes': { 'banner': {'sizes': ['300x250'] + }, + 'native': + { 'image': { + 'required': true, + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'privacyIcon': { + 'required': false + }, + 'privacyLink': { + 'required': false + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [] } + }, }, 'transactionId': 'bid_id_0_transaction_id' } ]; + const bidRequestWithNativeImageType = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': + { + 'native': { + 'type': 'image', + 'additional': { + 'will': 'be', + 'sent': ['300x250'] + } + }, + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const sentBidNative = { + 'bid_id_0': { + 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': 'e8355240-d976-4cd5-a493-640656fe08e8', + 'AvailableSizes': '', + 'Native': { + 'image': { + 'required': true, + 'sizes': [] + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'privacyIcon': { + 'required': false + }, + 'privacyLink': { + 'required': false + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [] + } + } + } + }; + + const sentBidVideo = { + 'bid_id_0': { + 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': 'e8355240-d976-4cd5-a493-640656fe08e8', + 'AvailableSizes': '', + 'Video': { + playerSize: [640, 480] + } + } + }; + + const sentNativeImageType = { + 'additional': { + 'sent': [ + '300x250' + ], + 'will': 'be' + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': true + }, + 'cta': { + 'required': false + }, + 'icon': { + 'required': false + }, + 'image': { + 'required': true + }, + 'sponsoredBy': { + 'required': true + }, + 'title': { + 'required': true + }, + 'type': 'image' + }; + const bidRequestWithDCPlacement = [ { 'bidId': 'bid_id_0', @@ -165,15 +301,120 @@ describe('Adyoulike Adapter', function () { 'Placement': 'placement_0' } ]; + + 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\":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 responseWithSinglePlacement = [ { 'BidID': 'bid_id_0', 'Placement': 'placement_0', - 'Ad': 'placement_0', + '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 + }, + '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, + }, + 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', @@ -214,15 +455,49 @@ 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', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + mediaTypes: { + native: { + + } + }, + 'transactionId': 'bid_id_1_transaction_id' + }; + it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + 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); }); it('should return false when required params are not passed', function () { let bid = Object.assign({}, bid); delete bid.size; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required params are not passed', function () { @@ -231,7 +506,7 @@ describe('Adyoulike Adapter', function () { bid.params = { 'placement': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(bid)).to.equal(false); }); }); @@ -250,6 +525,23 @@ describe('Adyoulike Adapter', function () { canonicalQuery.restore(); }); + it('Should expand short native image config type', function() { + const request = spec.buildRequests(bidRequestWithNativeImageType, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); + expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); + + 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'); + expect(payload.Bids['bid_id_0'].Native).deep.equal(sentNativeImageType); + }); + it('should add gdpr/usp consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let uspConsentData = '1YCC'; @@ -306,6 +598,7 @@ describe('Adyoulike Adapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); + expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); @@ -383,9 +676,10 @@ describe('Adyoulike Adapter', function () { expect(result.length).to.equal(1); expect(result[0].cpm).to.equal(0.5); - expect(result[0].ad).to.equal('placement_0'); + 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 () { @@ -404,5 +698,37 @@ describe('Adyoulike Adapter', function () { expect(result[1].width).to.equal(400); expect(result[1].height).to.equal(250); }); + + 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(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); + }); }); }); 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/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 790f3bf2581..f502d631c17 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -1,7 +1,9 @@ import * as utils from 'src/utils.js'; +import { createEidsArray } from 'modules/userId/eids.js'; import { expect } from 'chai'; import { spec } from 'modules/amxBidAdapter.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; const sampleRequestId = '82c91e127a9b93e'; const sampleDisplayAd = (additionalImpressions) => `${additionalImpressions}`; @@ -14,6 +16,30 @@ const sampleVideoAd = (addlImpression) => ` const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B=20903`; const sampleNurl = 'https://example.exchange/nurl'; +const sampleFPD = { + site: { + keywords: 'sample keywords', + ext: { + data: { + pageType: 'article' + } + } + }, + user: { + gender: 'O', + yob: 1982, + } +}; + +const stubConfig = (withStub) => { + const stub = sinon.stub(config, 'getConfig').callsFake( + (arg) => arg === 'ortb2' ? sampleFPD : null + ) + + withStub(); + stub.restore(); +}; + const sampleBidderRequest = { gdprConsent: { gdprApplies: true, @@ -34,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -45,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -80,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -99,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -120,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -155,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -165,12 +231,98 @@ describe('AmxBidAdapter', () => { expect(data.tm).to.equal(true); }); - it('handles referer data and GDPR, USP Consent', () => { + it('if prebid is in an iframe, will use the frame url as domain, if the topmost is not avialable', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + numIframes: 1, + referer: 'http://search-traffic-source.com', + stack: [] + } + }); + expect(data.do).to.equal('localhost') + expect(data.re).to.equal('http://search-traffic-source.com'); + }); + + it('if we are in AMP, make sure we use the canonical URL or the referrer (which is sourceUrl)', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + isAmp: true, + referer: 'http://real-publisher-site.com/content', + stack: [] + } + }); + expect(data.do).to.equal('real-publisher-site.com') + expect(data.re).to.equal('http://real-publisher-site.com/content'); + }) + + it('if prebid is in an iframe, will use the topmost url as domain', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + numIframes: 1, + referer: 'http://search-traffic-source.com', + stack: ['http://top-site.com', 'http://iframe.com'] + } + }); + expect(data.do).to.equal('top-site.com'); + expect(data.re).to.equal('http://search-traffic-source.com'); + }); + + it('handles referer data and GDPR, USP Consent, COPPA', () => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); delete data.m; // don't deal with "m" in this test expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies) expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString) expect(data.usp).to.equal(sampleBidderRequest.uspConsent) + expect(data.cpp).to.equal(0) + }); + + it('will forward bid request count & wins count data', () => { + const bidderRequestsCount = Math.floor(Math.random() * 100) + const bidderWinsCount = Math.floor(Math.random() * 100) + const { data } = spec.buildRequests([{ + ...sampleBidRequestBase, + bidderRequestsCount, + bidderWinsCount + }], sampleBidderRequest); + + expect(data.brc).to.equal(bidderRequestsCount) + expect(data.bwc).to.equal(bidderWinsCount) + expect(data.trc).to.equal(0) + }); + it('will forward first-party data', () => { + stubConfig(() => { + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + expect(data.fpd2).to.deep.equal(sampleFPD) + }); + }); + + it('will collect & forward RTI user IDs', () => { + const randomRTI = `greatRTI${Math.floor(Math.random() * 100)}` + const userId = { + britepoolid: 'sample-britepool', + criteoId: 'sample-criteo', + digitrustid: {data: {id: 'sample-digitrust'}}, + id5id: {uid: 'sample-id5'}, + idl_env: 'sample-liveramp', + lipb: {lipbid: 'sample-liveintent'}, + netId: 'sample-netid', + parrableId: { eid: 'sample-parrable' }, + pubcid: 'sample-pubcid', + [randomRTI]: 'sample-unknown', + tdid: 'sample-ttd', + }; + + const eids = createEidsArray(userId); + const bid = { + ...sampleBidRequestBase, + userIdAsEids: eids + }; + + const { data } = spec.buildRequests([bid, bid], sampleBidderRequest); + expect(data.eids).to.deep.equal(eids) }); it('can build a banner request', () => { @@ -188,17 +340,35 @@ describe('AmxBidAdapter', () => { expect(Object.keys(data.m).length).to.equal(2); expect(data.m[sampleRequestId]).to.deep.equal({ av: true, + au: 'div-gpt-ad-example', + vd: {}, + ms: [ + [[320, 50]], + [[300, 250]], + [] + ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, + au: 'div-gpt-ad-example', + sc: {}, + ms: [ + [[320, 50]], + [[300, 250]], + [] + ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -207,10 +377,24 @@ describe('AmxBidAdapter', () => { const { data } = spec.buildRequests([sampleBidRequestVideo], sampleBidderRequest); expect(Object.keys(data.m).length).to.equal(1); expect(data.m[sampleRequestId + '_video']).to.deep.equal({ + au: 'div-gpt-ad-example', + ms: [ + [[300, 150]], + [], + [[360, 250]] + ], av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -255,9 +439,10 @@ describe('AmxBidAdapter', () => { ...baseBidResponse.meta, mediaType: BANNER, }, + mediaType: BANNER, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -268,20 +453,14 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + mediaType: VIDEO, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, 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 cca175c3388..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 () { @@ -177,15 +178,69 @@ describe('ANIVIEW Bid Adapter Test', function () { let result = spec.interpretResponse(nobidResponse, bidRequest); expect(result.length).to.equal(0); }); + + it('should add renderer if outstream context', function () { + const bidRequest = spec.buildRequests([ + { + bidId: '253dcb69fb2577', + params: { + playerDomain: 'example.com', + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568' + }, + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + } + } + } + ])[0] + const bidResponse = spec.interpretResponse(serverResponse, bidRequest)[0] + + expect(bidResponse.renderer.url).to.equal('https://example.com/script/6.1/prebidRenderer.js') + expect(bidResponse.renderer.config.AV_PUBLISHERID).to.equal('55b78633181f4603178b4568') + expect(bidResponse.renderer.config.AV_CHANNELID).to.equal('55b7904d181f46410f8b4568') + 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} ]; @@ -195,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 dd10a57bbfe..50622180ad0 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import * as utils from 'src/utils.js'; import {spec} from 'modules/aolBidAdapter.js'; -import {config} from 'src/config.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const DEFAULT_AD_CONTENT = ''; @@ -80,6 +80,33 @@ describe('AolAdapter', function () { const NEXAGE_URL = 'https://c2shb.ssp.yahoo.com/bidRequest?'; const ONE_DISPLAY_TTL = 60; const ONE_MOBILE_TTL = 3600; + const SUPPORTED_USER_ID_SOURCES = { + 'adserver.org': '100', + 'criteo.com': '200', + 'id5-sync.com': '300', + 'intentiq.com': '400', + 'liveintent.com': '500', + 'quantcast.com': '600', + 'verizonmedia.com': '700', + 'liveramp.com': '800' + }; + + const USER_ID_DATA = { + criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], + connectid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], + idl_env: SUPPORTED_USER_ID_SOURCES['liveramp.com'], + lipb: { + lipbid: SUPPORTED_USER_ID_SOURCES['liveintent.com'], + segments: ['100', '200'] + }, + tdid: SUPPORTED_USER_ID_SOURCES['adserver.org'], + id5id: { + uid: SUPPORTED_USER_ID_SOURCES['id5-sync.com'], + ext: {foo: 'bar'} + }, + intentIqId: SUPPORTED_USER_ID_SOURCES['intentiq.com'], + quantcastId: SUPPORTED_USER_ID_SOURCES['quantcast.com'] + }; function createCustomBidRequest({bids, params} = {}) { var bidderRequest = getDefaultBidRequest(); @@ -133,6 +160,9 @@ describe('AolAdapter', function () { currency: 'USD', dealId: 'deal-id', netRevenue: true, + meta: { + advertiserDomains: [] + }, ttl: bidRequest.ttl }); }); @@ -339,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: { @@ -463,6 +481,18 @@ describe('AolAdapter', function () { '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); }); + Object.keys(SUPPORTED_USER_ID_SOURCES).forEach(source => { + it(`should set the user ID query param for ${source}`, function () { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() + }); + bidRequest.bids[0].userId = {}; + bidRequest.bids[0].userIdAsEids = createEidsArray(USER_ID_DATA); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(`&eid${source}=${encodeURIComponent(SUPPORTED_USER_ID_SOURCES[source])}`); + }); + }); + it('should return request object for One Mobile POST endpoint when POST configuration is present', function () { let bidConfig = getNexagePostBidParams(); let bidRequest = createCustomBidRequest({ @@ -581,6 +611,22 @@ describe('AolAdapter', function () { } }); }); + + it('returns the bid object with eid array populated with PB set eids', () => { + let userIdBid = Object.assign({ + userId: {} + }, bid); + userIdBid.userIdAsEids = createEidsArray(USER_ID_DATA); + expect(spec.buildOpenRtbRequestData(userIdBid)).to.deep.equal({ + id: 'bid-id', + imp: [], + user: { + ext: { + eids: userIdBid.userIdAsEids + } + } + }); + }); }); describe('getUserSyncs()', function () { @@ -723,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/quantumdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js similarity index 69% rename from test/spec/modules/quantumdexBidAdapter_spec.js rename to test/spec/modules/apacdexBidAdapter_spec.js index 82429cbedae..9b75481ada9 100644 --- a/test/spec/modules/quantumdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,14 +1,16 @@ import { expect } from 'chai' -import { spec } from 'modules/quantumdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' 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('QuantumdexBidAdapter', function () { +describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) describe('.code', function () { - it('should return a bidder code of quantumdex', function () { - expect(spec.code).to.equal('quantumdex') + it('should return a bidder code of apacdex', function () { + expect(spec.code).to.equal('apacdex') }) }) @@ -21,7 +23,7 @@ describe('QuantumdexBidAdapter', function () { describe('.isBidRequestValid', function () { it('should return false if there are no params', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', 'mediaTypes': { banner: { @@ -35,12 +37,13 @@ describe('QuantumdexBidAdapter', 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': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { site_id: '1a2b3c4d5e6f1a2b3c4d', + placement_id: 'plcm12345678', }, 'mediaTypes': { banner: { @@ -56,7 +59,7 @@ describe('QuantumdexBidAdapter', function () { it('should return false if there is no mediaTypes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -72,7 +75,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if the bid is valid', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -92,7 +95,7 @@ describe('QuantumdexBidAdapter', function () { describe('banner', () => { it('should return false if there are no banner sizes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -111,7 +114,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if there is banner sizes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -132,7 +135,7 @@ describe('QuantumdexBidAdapter', function () { describe('video', () => { it('should return false if there is no playerSize defined in the video mediaType', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d', @@ -152,7 +155,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if there is playerSize defined on the video mediaType', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d', @@ -196,17 +199,31 @@ describe('QuantumdexBidAdapter', function () { }, ] }, - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': { 'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60 } }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }], }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'ad_unit': '/7780971/sparks_prebid_LB', 'sizes': [[300, 250], [300, 600]], @@ -235,25 +252,25 @@ describe('QuantumdexBidAdapter', function () { it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.bidderRequests).to.eql(bidRequest); }) it('should return a properly formatted request with GDPR applies set to true', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') - expect(bidRequests.data.gdpr.gdprApplies).to.equal('true') + expect(bidRequests.data.gdpr.gdprApplies).to.equal(true) expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') }) it('should return a properly formatted request with GDPR applies set to false', function () { bidderRequests.gdprConsent.gdprApplies = false; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') - expect(bidRequests.data.gdpr.gdprApplies).to.equal('false') + expect(bidRequests.data.gdpr.gdprApplies).to.equal(false) expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') }) it('should return a properly formatted request with GDPR applies set to false with no consent_string param', function () { @@ -271,9 +288,9 @@ describe('QuantumdexBidAdapter', function () { } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') - expect(bidRequests.data.gdpr.gdprApplies).to.equal('false') + expect(bidRequests.data.gdpr.gdprApplies).to.equal(false) expect(bidRequests.data.gdpr).to.not.include.keys('consentString') }) it('should return a properly formatted request with GDPR applies set to true with no consentString param', function () { @@ -291,25 +308,83 @@ describe('QuantumdexBidAdapter', function () { } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') - expect(bidRequests.data.gdpr.gdprApplies).to.equal('true') + expect(bidRequests.data.gdpr.gdprApplies).to.equal(true) expect(bidRequests.data.gdpr).to.not.include.keys('consentString') }) it('should return a properly formatted request with schain defined', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) + expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) + }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + 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 }); + }); + it('should return a properly formatted request with pbjs_debug is true', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1); + }); + }); }); describe('.interpretResponse', function () { const bidRequests = { 'method': 'POST', - 'url': 'https://useast.quantumdex.io/auction/quantumdex', + 'url': 'https://useast.quantumdex.io/auction/apacdex', 'withCredentials': true, 'data': { 'device': { @@ -328,7 +403,7 @@ describe('QuantumdexBidAdapter', function () { }, 'bidderRequests': [ { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -363,7 +438,7 @@ describe('QuantumdexBidAdapter', function () { } }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -398,7 +473,7 @@ describe('QuantumdexBidAdapter', function () { } }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -464,39 +539,48 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.07, 'width': 160, 'height': 600, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', - 'mediaType': 'banner' + 'dealId': 'apacdex', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }, { 'requestId': '30024615be22ef66a', 'cpm': 1, 'width': 300, 'height': 250, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', - 'mediaType': 'banner' + 'dealId': 'apacdex', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } }, { 'requestId': '1854b40107d6745c', 'cpm': 1.25, 'width': 300, 'height': 250, - 'vastXml': 'quantumdex', + 'vastXml': 'apacdex', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', - 'mediaType': 'video' + 'dealId': 'apacdex', + 'mediaType': 'video', + 'meta': { + 'advertiserDomains': ['https://example.com'] + } } ], 'pixel': [{ @@ -512,12 +596,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.07, 'width': 160, 'height': 600, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -525,12 +609,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1, 'width': 300, 'height': 250, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -538,12 +622,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.25, 'width': 300, 'height': 250, - 'vastXml': 'quantumdex', + 'vastXml': 'apacdex', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'video' } ]; @@ -561,11 +645,12 @@ describe('QuantumdexBidAdapter', function () { expect(resp.currency).to.equal(prebidResponse[i].currency); expect(resp.dealId).to.equal(prebidResponse[i].dealId); if (resp.mediaType === 'video') { - expect(resp.vastXml.indexOf('quantumdex')).to.be.greaterThan(0); + expect(resp.vastXml.indexOf('apacdex')).to.be.greaterThan(0); } if (resp.mediaType === 'banner') { - expect(resp.ad.indexOf('Quantumdex AD')).to.be.greaterThan(0); + expect(resp.ad.indexOf('Apacdex AD')).to.be.greaterThan(0); } + expect(resp.meta.advertiserDomains).to.deep.equal(['https://example.com']); }); }); }); @@ -601,4 +686,66 @@ describe('QuantumdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + 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 }); + 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: '' }); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + 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 7e985045c45..9396c1e1928 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: { @@ -265,6 +299,7 @@ describe('AppNexusAdapter', function () { placementId: '10433394', user: { externalUid: '123', + segments: [123, { id: 987, value: 876 }], foobar: 'invalid' } } @@ -277,9 +312,40 @@ describe('AppNexusAdapter', function () { expect(payload.user).to.exist; expect(payload.user).to.deep.equal({ external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] }); }); + it('should attach reserve param when either bid param or getFloor function exists', function () { + let getFloorResponse = { currency: 'USD', floor: 3 }; + let request, payload = null; + let bidRequest = deepClone(bidRequests[0]); + + // 1 -> reserve not defined, getFloor not defined > empty + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.not.exist; + + // 2 -> reserve is defined, getFloor not defined > reserve is used + bidRequest.params = { + 'placementId': '10433394', + 'reserve': 0.5 + }; + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); + + // 3 -> reserve is defined, getFloor is defined > getFloor is used + bidRequest.getFloor = () => getFloorResponse; + + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + }); + it('should duplicate adpod placements into batches and set correct maxduration', function() { let bidRequest = Object.assign({}, bidRequests[0], @@ -627,6 +693,17 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].use_pmt_rule).to.equal(true); }); + it('should add gpid to the request', function () { + let testGpid = '/12345/my-gpt-tag-0'; + let bidRequest = deepClone(bidRequests[0]); + bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) + }); + it('should add gdpr consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { @@ -636,18 +713,20 @@ 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; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.options).to.be.empty; + expect(request.options).to.deep.equal({withCredentials: true}); 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; + 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() { @@ -782,7 +861,25 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); - it('should set withCredentials to false if purpose 1 consent is not given', function () { + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + + it('should always set withCredentials: true on the request.options', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest]); + expect(request.options.withCredentials).to.equal(true); + }); + + it('should set simple domain variant if purpose 1 consent is not given', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { 'bidderCode': 'appnexus', @@ -805,14 +902,21 @@ describe('AppNexusAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.options).to.deep.equal({withCredentials: false}); + expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); }); - it('should populate eids array when ttd id and criteo is available', function () { + it('should populate eids when supported userIds are available', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', - criteoId: 'sample-criteo-userid' + uid2: { id: 'sample-uid2-value' }, + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } } }); @@ -828,6 +932,73 @@ describe('AppNexusAdapter', function () { source: 'criteo.com', 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', + }); + + 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 () { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } + } + }); + let request = spec.buildRequests([bidRequest_A]); + let payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + + // without bid.params.frameworks + const bidRequest_B = Object.assign({}, bidRequests[0]); + request = spec.buildRequests([bidRequest_B]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } + } + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; }); }) @@ -1161,6 +1332,21 @@ describe('AppNexusAdapter', function () { } let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); - }) + }); + + 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/apstreamBidAdapter_spec.js b/test/spec/modules/apstreamBidAdapter_spec.js new file mode 100644 index 00000000000..e640c009989 --- /dev/null +++ b/test/spec/modules/apstreamBidAdapter_spec.js @@ -0,0 +1,346 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/apstreamBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const validBidRequests = [{ + bidId: 'bidId', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 120], [980, 180]], + mediaTypes: { + banner: { + sizes: [[980, 120], [980, 180]] + } + }, + params: { + publisherId: '1234' + } +}]; + +describe('AP Stream adapter', function() { + describe('isBidRequestValid', function() { + const bid = { + bidder: 'apstream', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + params: { + } + }; + + let mockConfig; + beforeEach(function () { + mockConfig = { + apstream: { + publisherId: '4321' + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should return true when publisherId is configured and one media type', function() { + bid.params.publisherId = '1234'; + assert(spec.isBidRequestValid(bid)) + }); + + it('should return false when publisherId is configured and two media types', function() { + bid.mediaTypes.video = {sizes: [300, 250]}; + assert.isFalse(spec.isBidRequestValid(bid)) + }); + + it('should return true when publisherId is configured via config', function() { + delete bid.mediaTypes.video; + delete bid.params.publisherId; + assert.isTrue(spec.isBidRequestValid(bid)) + }); + }); + + describe('buildRequests', function() { + it('should send request with correct structure', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.method, 'GET'); + assert.deepEqual(request.options, {withCredentials: false}); + assert.ok(request.data); + }); + + it('should send request with different endpoints', function() { + const validTwoBidRequests = [ + ...validBidRequests, + ...[{ + bidId: 'bidId2', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 980], [980, 900]], + mediaTypes: { + banner: { + sizes: [[980, 980], [980, 900]] + } + }, + params: { + publisherId: '1234', + endpoint: 'site2.com' + } + }] + ]; + + const request = spec.buildRequests(validTwoBidRequests, {}); + assert.isArray(request); + assert.lengthOf(request, 2); + assert.equal(request[1].data.bids, 'bidId2:t=b,s=980x980_980x900,c=/id/site1/header-ad'); + }); + + it('should send request with adUnit code', function() { + const adunitCodeValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + code: 'Site1_Leaderboard' + } + } + } + ]; + + const request = spec.buildRequests(adunitCodeValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,c=Site1_Leaderboard'); + }); + + it('should send request with adUnit id', function() { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + adunitId: '12345' + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,u=12345'); + }); + + it('should send request with different media type', function() { + const types = { + 'audio': 'a', + 'banner': 'b', + 'native': 'n', + 'video': 'v' + } + Object.keys(types).forEach(key => { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + mediaTypes: { + [key]: { + sizes: [300, 250] + } + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, `bidId:t=${types[key]},s=980x120_980x180,c=/id/site1/header-ad`); + }) + }); + + describe('gdpr', function() { + let mockConfig; + + beforeEach(function () { + mockConfig = { + consentManagement: { + cmpApi: 'iab' + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send GDPR Consent data', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + assert.equal(request.iab_consent, bidderRequest.gdprConsent.consentString); + }); + }); + + describe('dsu', function() { + it('should pass DSU from local storage if set', function() { + let dsu = 'some_dsu'; + localStorage.setItem('apr_dsu', dsu); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + assert.equal(request.dsu, dsu); + }); + + it('should generate new DSU if nothing in local storage', function() { + localStorage.removeItem('apr_dsu'); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + let dsu = localStorage.getItem('apr_dsu'); + + assert.isNotEmpty(dsu); + assert.equal(request.dsu, dsu); + }); + }); + }); + + describe('dsu config', function() { + let mockConfig; + beforeEach(function () { + mockConfig = { + apstream: { + noDsu: true + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should not send DSU if it is disabled in config', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.data.dsu, ''); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array if no body in response', function () { + const serverResponse = {}; + const bidRequest = {}; + + assert.isEmpty(spec.interpretResponse(serverResponse, bidRequest)); + }); + + it('should map server response', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + netRevenue: 'true', + creativeId: '1234', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + const expected = { + requestId: 123, + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + }; + + assert.deepEqual(response[0], expected); + }); + + it('should add pixels to ad', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360, + noticeUrls: [ + 'site1', + 'site2' + ], + impressionScripts: [ + 'url_to_script' + ] + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + assert.match(response[0].ad, /site1/); + assert.match(response[0].ad, /site2/); + }); + }); +}); 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/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js index e422f64b570..0e545081869 100644 --- a/test/spec/modules/astraoneBidAdapter_spec.js +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -14,7 +14,7 @@ function getSlotConfigs(mediaTypes, params) { describe('AstraOne Adapter', function() { describe('isBidRequestValid method', function() { - const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const PLACE_ID = '5f477bf94d506ebe2c4240f3'; const IMAGE_URL = 'https://creative.astraone.io/files/default_image-1-600x400.jpg'; describe('returns true', function() { @@ -176,21 +176,23 @@ describe('AstraOne Adapter', function() { describe('the bid is a banner', function() { it('should return a banner bid', function() { const serverResponse = { - body: [ - { - bidId: '2df8c0733f284e', - price: 0.5, - currency: 'USD', - content: { - content: 'html', - actionUrls: {}, - seanceId: '123123' - }, - width: 100, - height: 100, - ttl: 360 - } - ] + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + actionUrls: {}, + seanceId: '123123' + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } } const bids = spec.interpretResponse(serverResponse) expect(bids.length).to.equal(1) 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 849ccf88c51..e2dd4747199 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -2,27 +2,40 @@ import atsAnalyticsAdapter from '../../../modules/atsAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; -import {checkUserBrowser, browserIsChrome, browserIsEdge, browserIsSafari, browserIsFirefox} from '../../../modules/atsAnalyticsAdapter.js'; +import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; +import {getStorageManager} from '../../../src/storageManager.js'; +import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; + let events = require('src/events'); let constants = require('src/constants.json'); +export const storage = getStorageManager(); + 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'); }); afterEach(function () { events.getEvents.restore(); + atsAnalyticsAdapter.getUserAgent.restore(); atsAnalyticsAdapter.disableAnalytics(); + Math.random.restore(); }); describe('track', function () { it('builds and sends request and response data', function () { - sinon.spy(atsAnalyticsAdapter, 'track'); + sinon.stub(Math, 'random').returns(0.99); + sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true); + 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()); let initOptions = { - pid: '10433394', - host: 'https://example.com/dev', + pid: '10433394' }; let auctionTimestamp = 1496510254326; @@ -74,13 +87,15 @@ describe('ats analytics adapter', function () { let expectedAfterBid = { 'Data': [{ 'has_envelope': true, + 'adapter_version': 1, 'bidder': 'appnexus', 'bid_id': '30c77d079cdf17', 'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7', - 'user_browser': checkUserBrowser(), + 'user_browser': parseBrowser(), 'user_platform': navigator.platform, 'auction_start': '2020-02-03T14:14:25.161Z', 'domain': window.location.hostname, + 'envelope_source': true, 'pid': '10433394', 'response_time_stamp': '2020-02-03T14:23:11.978Z', 'currency': 'USD', @@ -132,77 +147,63 @@ describe('ats analytics adapter', function () { events.emit(constants.EVENTS.AUCTION_END, {}); let requests = server.requests.filter(req => { - return req.url.indexOf(initOptions.host) > -1; + return req.url.indexOf(analyticsUrl) > -1; }); expect(requests.length).to.equal(1); let realAfterBid = JSON.parse(requests[0].requestBody); - // Step 6: assert real data after bid and expected data expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']); - // check that the host and publisher ID is configured via options - expect(atsAnalyticsAdapter.context.host).to.equal(initOptions.host); + // check that the publisher ID is configured via options expect(atsAnalyticsAdapter.context.pid).to.equal(initOptions.pid); - }) - it('check browser is not safari', function () { - window.safari = undefined; - let browser = browserIsSafari(); - expect(browser).to.equal(false); + + atsAnalyticsAdapter.shouldFireRequest.restore(); }) it('check browser is safari', function () { - window.safari = {}; - let browser = browserIsSafari(); + 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 not chrome', function () { - window.chrome = { - app: undefined, - webstore: undefined, - runtime: undefined - }; - let browser = browserIsChrome(); - expect(browser).to.equal(false); - }) it('check browser is chrome', function () { - window.chrome = { - app: {}, - webstore: {}, - runtime: {} - }; - let browser = browserIsChrome(); + 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 () { - Object.defineProperty(window, 'StyleMedia', { - value: {}, - writable: true - }); - Object.defineProperty(document, 'documentMode', { - value: undefined, - writable: true - }); - let browser = browserIsEdge(); - expect(browser).to.equal('Edge'); - }) - it('check browser is not edge', function () { - Object.defineProperty(document, 'documentMode', { - value: {}, - writable: true - }); - let browser = browserIsEdge(); - expect(browser).to.equal(false); + 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 () { - global.InstallTrigger = {}; - let browser = browserIsFirefox(); + 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 not firefox', function () { - global.InstallTrigger = undefined; - let browser = browserIsFirefox(); - expect(browser).to.equal(false); + 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(10); + 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); }) }) }) diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js deleted file mode 100644 index 04694731981..00000000000 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ /dev/null @@ -1,568 +0,0 @@ -/** - * @file Tests for AudienceNetwork adapter. - */ -import { expect } from 'chai'; - -import { spec } from 'modules/audienceNetworkBidAdapter.js'; -import * as utils from 'src/utils.js'; - -const { - code, - supportedMediaTypes, - isBidRequestValid, - buildRequests, - interpretResponse -} = spec; - -const bidder = 'audienceNetwork'; -const placementId = 'test-placement-id'; -const playerwidth = 320; -const playerheight = 180; -const requestId = 'test-request-id'; -const debug = 'adapterver=1.3.0&platform=241394079772386&platver=$prebid.version$&cb=test-uuid'; -const expectedPageUrl = encodeURIComponent('http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true'); -const mockBidderRequest = { - bidderCode: 'audienceNetwork', - auctionId: '720146a0-8f32-4db0-bb30-21f1d057efb4', - bidderRequestId: '1312d48561657e', - auctionStart: 1579711852920, - timeout: 3000, - refererInfo: { - referer: 'http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true', - reachedTop: true, - numIframes: 0, - stack: ['http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true'], - canonicalUrl: undefined - }, - start: 1579711852925 -}; - -describe('AudienceNetwork adapter', function () { - describe('Public API', function () { - it('code', function () { - expect(code).to.equal(bidder); - }); - it('supportedMediaTypes', function () { - expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); - }); - it('isBidRequestValid', function () { - expect(isBidRequestValid).to.be.a('function'); - }); - it('buildRequests', function () { - expect(buildRequests).to.be.a('function'); - }); - it('interpretResponse', function () { - expect(interpretResponse).to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('missing placementId parameter', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250]] - })).to.equal(false); - }); - - it('invalid sizes parameter', function () { - expect(isBidRequestValid({ - bidder, - sizes: ['', undefined, null, '300x100', [300, 100], [300], {}], - params: { placementId } - })).to.equal(false); - }); - - it('valid when at least one valid size', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[1, 1], [300, 250]], - params: { placementId } - })).to.equal(true); - }); - - it('valid parameters', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250], [320, 50]], - params: { placementId } - })).to.equal(true); - }); - - it('fullwidth', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250], [336, 280]], - params: { - placementId, - format: 'fullwidth' - } - })).to.equal(true); - }); - - it('native', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250]], - params: { - placementId, - format: 'native' - } - })).to.equal(true); - }); - - it('native with non-IAB size', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[728, 90]], - params: { - placementId, - format: 'native' - } - })).to.equal(true); - }); - - it('video', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[playerwidth, playerheight]], - params: { - placementId, - format: 'video' - } - })).to.equal(true); - }); - }); - - describe('buildRequests', function () { - before(function () { - sinon - .stub(utils, 'generateUUID') - .returns('test-uuid'); - }); - - after(function () { - utils.generateUUID.restore(); - }); - - it('takes the canonicalUrl as priority over referer for pageurl', function () { - let expectedCanonicalUrl = encodeURIComponent('http://www.this-is-canonical-url.com/hello-world.html?pbjs_debug=true'); - mockBidderRequest.refererInfo.canonicalUrl = 'http://www.this-is-canonical-url.com/hello-world.html?pbjs_debug=true'; - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 50], [300, 250], [320, 50]], - params: { placementId } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedCanonicalUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedCanonicalUrl}&sdk[]=6.0.web&${debug}` - }]); - mockBidderRequest.refererInfo.canonicalUrl = undefined; - }); - - it('can build URL for IAB unit', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 50], [300, 250], [320, 50]], - params: { placementId } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debug}` - }]); - }); - - it('can build URL for video unit', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[640, 480]], - params: { - placementId, - format: 'video' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['video'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['640x480'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=video&testmode=false&pageurl=${expectedPageUrl}&sdk[]=&${debug}&playerwidth=640&playerheight=480` - }]); - }); - - it('can build URL for native unit in non-IAB size', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[728, 90]], - params: { - placementId, - format: 'native' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['native'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['728x90'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debug}` - }]); - }); - - it('can build URL for deprecated fullwidth unit, overriding platform', function () { - const platform = 'test-platform'; - const debugPlatform = debug.replace('241394079772386', platform); - - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 250]], - params: { - placementId, - platform, - format: 'fullwidth' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debugPlatform}` - }]); - }); - }); - - describe('interpretResponse', function () { - it('error in response', function () { - expect(interpretResponse({ - body: { - errors: ['test-error-message'] - } - }, {})).to.deep.equal([]); - }); - - it('valid native bid in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') - .and.to.contain('
', 'ad missing native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('native'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('valid IAB bid in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['300x250'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: '300x250',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.not.to.contain('getElementsByTagName("style")', 'ad should not contain native styles') - .and.not.to.contain('
', 'ad should not contain native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('300x250'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('filters invalid slot sizes', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['300x250'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('300x250'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('valid multiple bids in response', function () { - const placementIdNative = 'test-placement-id-native'; - const placementIdIab = 'test-placement-id-iab'; - - const [bidResponseNative, bidResponseIab] = interpretResponse({ - body: { - errors: [], - bids: { - [placementIdNative]: [{ - placement_id: placementIdNative, - bid_id: 'test-bid-id-native', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [placementIdIab]: [{ - placement_id: placementIdIab, - bid_id: 'test-bid-id-iab', - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native', '300x250'], - requestIds: [requestId, requestId], - sizes: ['300x250', [300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponseNative.cpm).to.equal(1.23); - expect(bidResponseNative.requestId).to.equal(requestId); - expect(bidResponseNative.width).to.equal(300); - expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad) - .to.contain(`placementid: '${placementIdNative}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id-native',`); - expect(bidResponseNative.ttl).to.equal(600); - expect(bidResponseNative.creativeId).to.equal(placementIdNative); - expect(bidResponseNative.netRevenue).to.equal(true); - expect(bidResponseNative.currency).to.equal('USD'); - expect(bidResponseNative.hb_bidder).to.equal('fan'); - expect(bidResponseNative.fb_bidid).to.equal('test-bid-id-native'); - expect(bidResponseNative.fb_format).to.equal('native'); - expect(bidResponseNative.fb_placementid).to.equal(placementIdNative); - - expect(bidResponseIab.cpm).to.equal(4.56); - expect(bidResponseIab.requestId).to.equal(requestId); - expect(bidResponseIab.width).to.equal(300); - expect(bidResponseIab.height).to.equal(250); - expect(bidResponseIab.ad) - .to.contain(`placementid: '${placementIdIab}',`) - .and.to.contain(`format: '300x250',`) - .and.to.contain(`bidid: 'test-bid-id-iab',`); - expect(bidResponseIab.ttl).to.equal(600); - expect(bidResponseIab.creativeId).to.equal(placementIdIab); - expect(bidResponseIab.netRevenue).to.equal(true); - expect(bidResponseIab.currency).to.equal('USD'); - expect(bidResponseIab.hb_bidder).to.equal('fan'); - expect(bidResponseIab.fb_bidid).to.equal('test-bid-id-iab'); - expect(bidResponseIab.fb_format).to.equal('300x250'); - expect(bidResponseIab.fb_placementid).to.equal(placementIdIab); - }); - - it('valid video bid in response', function () { - const bidId = 'test-bid-id-video'; - - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: bidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['video'], - requestIds: [requestId], - sizes: [[playerwidth, playerheight]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.ttl).to.equal(3600); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${placementId}&pageurl=${expectedPageUrl}&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${bidId}`); - expect(bidResponse.width).to.equal(playerwidth); - expect(bidResponse.height).to.equal(playerheight); - }); - - it('mixed video and native bids', function () { - const videoPlacementId = 'test-video-placement-id'; - const videoBidId = 'test-video-bid-id'; - const nativePlacementId = 'test-native-placement-id'; - const nativeBidId = 'test-native-bid-id'; - - const [bidResponseVideo, bidResponseNative] = interpretResponse({ - body: { - errors: [], - bids: { - [videoPlacementId]: [{ - placement_id: videoPlacementId, - bid_id: videoBidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [nativePlacementId]: [{ - placement_id: nativePlacementId, - bid_id: nativeBidId, - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['video', 'native'], - requestIds: [requestId, requestId], - sizes: [[playerwidth, playerheight], [300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponseVideo.cpm).to.equal(1.23); - expect(bidResponseVideo.requestId).to.equal(requestId); - expect(bidResponseVideo.ttl).to.equal(3600); - expect(bidResponseVideo.mediaType).to.equal('video'); - expect(bidResponseVideo.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${videoPlacementId}&pageurl=${expectedPageUrl}&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${videoBidId}`); - expect(bidResponseVideo.width).to.equal(playerwidth); - expect(bidResponseVideo.height).to.equal(playerheight); - - expect(bidResponseNative.cpm).to.equal(4.56); - expect(bidResponseNative.requestId).to.equal(requestId); - expect(bidResponseNative.ttl).to.equal(600); - expect(bidResponseNative.width).to.equal(300); - expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad) - .to.contain(`placementid: '${nativePlacementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: '${nativeBidId}',`); - }); - - it('mixture of valid native bid and error in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: ['test-error-message'], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') - .and.to.contain('
', 'ad missing native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('native'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - }); -}); 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 788fd3aefc4..9d828bad4c3 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -86,6 +86,11 @@ describe('automatadBidAdapter', function () { expect(rdata.imp.length).to.equal(1) }) + it('should include placement', function () { + let r = rdata.imp[0] + expect(r.placement !== null).to.be.true + }) + it('should include media types', function () { let r = rdata.imp[0] expect(r.media_types !== null).to.be.true @@ -95,12 +100,68 @@ describe('automatadBidAdapter', function () { let r = rdata.imp[0] expect(r.siteID !== null && r.placementID !== null).to.be.true }) + + it('should include adunit code', function () { + let r = rdata.imp[0] + expect(r.adUnitCode !== null).to.be.true + }) }) describe('interpretResponse', function () { it('should get the correct bid response', function () { let result = spec.interpretResponse(expectedResponse[0]) expect(result).to.be.an('array').that.is.not.empty + expect(result[0].meta.advertiserDomains[0]).to.equal('someAdDomain'); + }) + + it('should interpret multiple bids in seatbid', function () { + let multipleBidResponse = [{ + 'body': { + 'id': 'abc-321', + 'seatbid': [ + { + 'bid': [ + { + 'adm': '', + 'adomain': [ + 'someAdDomain' + ], + 'crid': 123, + 'h': 600, + 'id': 'bid1', + 'impid': 'imp1', + 'nurl': 'https://example/win', + 'price': 0.5, + 'w': 300 + } + ] + }, { + 'bid': [ + { + 'adm': '', + 'adomain': [ + 'someAdDomain' + ], + 'crid': 321, + 'h': 600, + 'id': 'bid1', + 'impid': 'imp2', + 'nurl': 'https://example/win', + 'price': 0.5, + 'w': 300 + } + ] + } + ] + } + }] + let result = spec.interpretResponse(multipleBidResponse[0]).map(bid => { + const {requestId} = bid; + return [ requestId ]; + }); + + assert.equal(result.length, 2); + assert.deepEqual(result, [[ 'imp1' ], [ 'imp2' ]]); }) it('handles empty bid response', function () { diff --git a/test/spec/modules/avocetBidAdapter_spec.js b/test/spec/modules/avocetBidAdapter_spec.js deleted file mode 100644 index 4cfd8ab89d4..00000000000 --- a/test/spec/modules/avocetBidAdapter_spec.js +++ /dev/null @@ -1,165 +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: '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/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js new file mode 100644 index 00000000000..a952d527600 --- /dev/null +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -0,0 +1,378 @@ +import { expect } from 'chai'; +import { spec } from 'modules/axonixBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('AxonixBidAdapter', function () { + const adapter = newBidder(spec); + + const SUPPLY_ID_1 = '91fd110a-5685-11eb-8db6-a7e0eeefbbc7'; + const SUPPLY_ID_2 = '22de2092-568b-11eb-bae3-cfa975dc72aa'; + const REGION_1 = 'us-east-1'; + const REGION_2 = 'eu-west-1'; + + const BANNER_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const VIDEO_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [400, 300], + renderer: { + url: 'https://url.com', + backupOnly: true, + render: () => true + }, + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const BIDDER_REQUEST = { + bidderCode: 'axonix', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const BANNER_RESPONSE = { + body: [{ + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'banner' + }, + nurl: 'https://win.url' + }] + }; + + const VIDEO_RESPONSE = { + body: [{ + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'video' + }, + nurl: 'https://win.url' + }] + }; + + 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 validBids = [ + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + }, + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_2, + region: REGION_2 + }, + future_parameter: { + future: 'ididid' + } + }, + ]; + + let invalidBids = [ + { + bidder: 'axonix', + params: {}, + }, + { + bidder: 'axonix', + }, + ]; + + it('should accept valid bids', function () { + for (let bid of validBids) { + expect(spec.isBidRequestValid(bid)).to.equal(true); + } + }); + + it('should reject invalid bids', function () { + for (let bid of invalidBids) { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + }); + + describe('buildRequests: can handle banner ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([BANNER_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', BANNER_REQUEST); + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[0].params.endpoint = 'https://the.url'; + bannerRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', bannerRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[1].params.supplyId = SUPPLY_ID_2; + bannerRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const bannerRequest = utils.deepClone(BANNER_REQUEST); + delete bannerRequest.params.region; + + const requests = spec.buildRequests([bannerRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe('buildRequests: can handle video ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([VIDEO_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', VIDEO_REQUEST); + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[0].params.endpoint = 'https://the.url'; + videoRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', videoRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[1].params.supplyId = SUPPLY_ID_2; + videoRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const videoRequest = utils.deepClone(VIDEO_REQUEST); + delete videoRequest.params.region; + + const requests = spec.buildRequests([videoRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe.skip('buildRequests: can handle native ad requests', function () { + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + // loop: + // set supply id + // set region/endpoint in ssp config + // call buildRequests, validate request (url, method, supply id) + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + // no endpoint in config means default value openrtb.axonix.com + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + // no region in config means default value us-east-1 + expect.fail('Not implemented'); + }); + }); + + describe('interpretResponse', function () { + it('considers corner cases', function() { + expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; + expect(spec.interpretResponse()).to.be.an('array').that.is.empty; + }); + + it('ignores unparseable responses', function() { + expect(spec.interpretResponse('invalid')).to.be.an('array').that.is.empty; + expect(spec.interpretResponse(['invalid'])).to.be.an('array').that.is.empty; + expect(spec.interpretResponse({ body: [{ invalid: 'object' }] })).to.be.an('array').that.is.empty; + }); + + it('parses banner responses', function () { + const response = spec.interpretResponse(BANNER_RESPONSE); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(BANNER_RESPONSE.body[0]); + }); + + it('parses 1 video responses', function () { + const response = spec.interpretResponse(VIDEO_RESPONSE); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(VIDEO_RESPONSE.body[0]); + }); + + it.skip('parses 1 native responses', function () { + // passing 1 valid native in a response generates an array with 1 correct prebid response + // examine mediaType:native, native element + // check nativeBidIsValid from {@link file://./../../../src/native.js} + expect.fail('Not implemented'); + }); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon(BANNER_RESPONSE.body[0]); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon({ cpm: '2.21' }); + expect(utils.triggerPixel.called).to.equal(false); + }); + + it('when there is no notification expected server side, none is called', function () { + var response = spec.onBidWon({}); + expect(utils.triggerPixel.called).to.equal(false); + expect(response).to.be.an('undefined') + }); + }); + + describe('onTimeout', function () { + it('banner response', () => { + spec.onTimeout(spec.interpretResponse(BANNER_RESPONSE)); + }); + + it('video response', () => { + spec.onTimeout(spec.interpretResponse(VIDEO_RESPONSE)); + }); + }); +}); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 5711e111e30..e29994eba44 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import sinon from 'sinon'; import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter.js'; import { parseUrl } from 'src/utils.js'; @@ -172,6 +171,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; @@ -223,17 +240,33 @@ describe('BeachfrontAdapter', function () { expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); }); - it('must override video targeting params', function () { + it('must set video params from the standard object', function () { const bidRequest = bidRequests[0]; const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; const placement = 4; - bidRequest.mediaTypes = { video: {} }; - bidRequest.params.video = { mimes, playbackmethod, maxduration, placement }; + const skip = 1; + bidRequest.mediaTypes = { + video: { mimes, playbackmethod, maxduration, placement, skip } + }; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + }); + + it('must override video params from the bidder object', function () { + const bidRequest = bidRequests[0]; + const mimes = ['video/webm']; + const playbackmethod = 2; + const maxduration = 30; + const placement = 4; + const skip = 1; + bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); }); it('must add US privacy data to the request', function () { @@ -262,22 +295,75 @@ describe('BeachfrontAdapter', function () { expect(data.user.ext.consent).to.equal(consentString); }); - it('must add the Trade Desk User ID to the request', () => { - const tdid = '4321'; + it('must add schain data to the request', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 + } + ] + }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; - bidRequest.userId = { tdid }; + bidRequest.schain = schain; 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' - } - }] - }); + expect(data.source.ext.schain).to.deep.equal(schain); + }); + + 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 = userId; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + 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, + }] + } + ]); }); }); @@ -302,6 +388,7 @@ describe('BeachfrontAdapter', function () { const width = 300; const height = 250; const bidRequest = bidRequests[0]; + bidRequest.params.tagid = '7cd7a7b4-ef3f-4aeb-9565-3627f255fa10'; bidRequest.mediaTypes = { banner: { sizes: [ width, height ] @@ -320,6 +407,7 @@ describe('BeachfrontAdapter', function () { slot: bidRequest.adUnitCode, id: bidRequest.params.appId, bidfloor: bidRequest.params.bidfloor, + tagid: bidRequest.params.tagid, sizes: [{ w: width, h: height }] } ]); @@ -329,6 +417,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; @@ -410,14 +516,43 @@ describe('BeachfrontAdapter', function () { expect(data.gdprConsent).to.equal(consentString); }); - it('must add the Trade Desk User ID to the request', () => { - const tdid = '4321'; + it('must add schain data to the request', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 + } + ] + }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; - bidRequest.userId = { tdid }; + bidRequest.schain = schain; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.tdid).to.equal(tdid); + expect(data.schain).to.deep.equal(schain); + }); + + 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 = userId; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + 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); }); }); @@ -486,16 +621,6 @@ describe('BeachfrontAdapter', function () { expect(bidResponse.length).to.equal(0); }); - it('should return no bids if the response "url" is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { video: {} }; - const serverResponse = { - bidPrice: 5.00 - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - it('should return no bids if the response "bidPrice" is missing', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; @@ -518,6 +643,7 @@ describe('BeachfrontAdapter', function () { const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + vast: '', crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); @@ -527,10 +653,11 @@ describe('BeachfrontAdapter', function () { cpm: serverResponse.bidPrice, creativeId: serverResponse.crid, vastUrl: serverResponse.url, - vastXml: undefined, + vastXml: serverResponse.vast, width: width, height: height, renderer: null, + meta: { mediaType: 'video', advertiserDomains: [] }, mediaType: 'video', currency: 'USD', netRevenue: true, @@ -538,7 +665,7 @@ describe('BeachfrontAdapter', function () { }); }); - it('should default to the legacy "cmpId" value for the creative ID', () => { + it('should return only vast url if the response type is "nurl"', () => { const width = 640; const height = 480; const bidRequest = bidRequests[0]; @@ -547,18 +674,19 @@ describe('BeachfrontAdapter', function () { playerSize: [ width, height ] } }; + bidRequest.params.video = { responseType: 'nurl' }; const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' + vast: '', + crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse).to.deep.contain({ - creativeId: serverResponse.cmpId - }); + expect(bidResponse.vastUrl).to.equal(serverResponse.url); + expect(bidResponse.vastXml).to.equal(undefined); }); - it('should return vast xml if found on the bid response', () => { + it('should return only vast xml if the response type is "adm"', () => { const width = 640; const height = 480; const bidRequest = bidRequests[0]; @@ -567,6 +695,7 @@ describe('BeachfrontAdapter', function () { playerSize: [ width, height ] } }; + bidRequest.params.video = { responseType: 'adm' }; const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', @@ -574,10 +703,8 @@ describe('BeachfrontAdapter', function () { crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse).to.deep.contain({ - vastUrl: serverResponse.url, - vastXml: serverResponse.vast - }); + expect(bidResponse.vastUrl).to.equal(undefined); + expect(bidResponse.vastXml).to.equal(serverResponse.vast); }); it('should return a renderer for outstream video bids', function () { @@ -597,63 +724,32 @@ describe('BeachfrontAdapter', function () { id: bidRequest.bidId, url: OUTSTREAM_SRC }); + expect(bidResponse.renderer.render).to.be.a('function'); }); - it('should initialize a player for outstream bids', () => { + it('should return meta data for the bid response', () => { const width = 640; const height = 480; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: { - context: 'outstream', playerSize: [ width, height ] } }; const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - crid: '123abc' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - window.Beachfront = { Player: sinon.spy() }; - bidResponse.adUnitCode = bidRequest.adUnitCode; - bidResponse.renderer.render(bidResponse); - sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match({ - adTagUrl: bidResponse.vastUrl, - width: bidResponse.width, - height: bidResponse.height, - expandInView: false, - collapseOnComplete: true - })); - delete window.Beachfront; - }); - - it('should configure outstream player settings from the bidder params', () => { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { - video: { - context: 'outstream', - playerSize: [ width, height ] + meta: { + advertiserDomains: ['example.com'], + advertiserId: '123' } }; - bidRequest.params.player = { - expandInView: true, - collapseOnComplete: false, - progressColor: 'green' - }; - const serverResponse = { - bidPrice: 5.00, - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - crid: '123abc' - }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - window.Beachfront = { Player: sinon.spy() }; - bidResponse.adUnitCode = bidRequest.adUnitCode; - bidResponse.renderer.render(bidResponse); - sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match(bidRequest.params.player)); - delete window.Beachfront; + expect(bidResponse.meta).to.deep.equal({ + mediaType: 'video', + advertiserDomains: ['example.com'], + advertiserId: '123' + }); }); }); @@ -709,6 +805,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, @@ -716,6 +813,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..b68adb8f196 --- /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://test.te'); + }); + }); + + 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 94db41ba014..7f8e69669a8 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('betweenBidAdapterTests', function () { sizes: [[240, 400]] }] let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.bidid).to.equal('bid1234'); }); it('validate itu param', function() { @@ -37,7 +37,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.itu).to.equal('https://something.url'); }); @@ -55,7 +55,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.cur).to.equal('THX'); }); @@ -73,10 +73,58 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; 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', @@ -91,7 +139,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.click3rd).to.equal('https://something.url'); }); @@ -111,7 +159,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data['pubside_macro[param]']).to.equal('%26test%3Dtset'); }); @@ -134,7 +182,7 @@ describe('betweenBidAdapterTests', function () { } let request = spec.buildRequests(bidRequestData, bidderRequest); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.gdprApplies).to.equal(bidderRequest.gdprConsent.gdprApplies); expect(req_data.consentString).to.equal(bidderRequest.gdprConsent.consentString); @@ -217,8 +265,42 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; + + expect(req_data.sizes).to.deep.equal(['970x250', '240x400', '728x90']) + }); - expect(req_data.sizes).to.deep.equal('970x250%2C240x400%2C728x90'); + 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 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..b59dbc867c1 --- /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 { EVENTS } 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(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 new file mode 100644 index 00000000000..211dec090a5 --- /dev/null +++ b/test/spec/modules/bidViewability_spec.js @@ -0,0 +1,297 @@ +import * as bidViewability from 'modules/bidViewability.js'; +import { config } from 'src/config.js'; +import * as events from 'src/events.js'; +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 adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; +import parse from 'url-parse'; + +const GPT_SLOT = { + getAdUnitPath() { + return '/harshad/Jan/2021/'; + }, + + getSlotElementId() { + return 'DIV-1'; + } +}; + +const PBJS_WINNING_BID = { + 'adUnitCode': '/harshad/Jan/2021/', + 'bidderCode': 'pubmatic', + 'bidder': 'pubmatic', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': 'id', + 'requestId': 1024, + 'source': 'client', + 'no_bid': false, + 'cpm': '1.1495', + 'ttl': 180, + 'creativeId': 'id', + 'netRevenue': true, + 'currency': 'USD', + 'vurls': [ + 'https://domain-1.com/end-point?a=1', + 'https://domain-2.com/end-point/', + 'https://domain-3.com/end-point?a=1' + ] +}; + +describe('#bidViewability', function() { + let gptSlot; + let pbjsWinningBid; + + beforeEach(function() { + gptSlot = Object.assign({}, GPT_SLOT); + pbjsWinningBid = Object.assign({}, PBJS_WINNING_BID); + }); + + describe('isBidAdUnitCodeMatchingSlot', function() { + it('match found by GPT Slot getAdUnitPath', function() { + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match found by GPT Slot getSlotElementId', function() { + pbjsWinningBid.adUnitCode = 'DIV-1'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match not found', function() { + pbjsWinningBid.adUnitCode = 'DIV-10'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(false); + }); + }); + + describe('getMatchingWinningBidForGPTSlot', function() { + let winningBidsArray; + let sandbox + beforeEach(function() { + sandbox = sinon.sandbox.create(); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('should find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + let newWinningBid = Object.assign({}, PBJS_WINNING_BID, {adUnitCode: 'AD-' + PBJS_WINNING_BID.adUnitCode}); + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(newWinningBid); + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.deep.equal(newWinningBid); + }); + + it('should NOT find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.equal(null); + }); + + it('should find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(PBJS_WINNING_BID); + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.deep.equal(PBJS_WINNING_BID); + }); + + it('should NOT find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.equal(null); + }); + }); + + describe('fireViewabilityPixels', function() { + let sandbox; + let triggerPixelSpy; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('DO NOT fire pixels if NOT mentioned in module config', function() { + let moduleConfig = {}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + expect(triggerPixelSpy.callCount).to.equal(0); + }); + + it('fire pixels if mentioned in module config', function() { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + }); + + it('USP: should include the us_privacy key when USP Consent is available', function () { + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal('1YYY'); + }); + uspDataHandlerStub.restore(); + }); + + it('USP: should not include the us_privacy key when USP Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal(undefined); + }); + }); + + it('GDPR: should include the GDPR keys when GDPR Consent is available', function() { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal('moreConsent'); + }); + gdprDataHandlerStub.restore(); + }); + + it('GDPR: should not include the GDPR keys when GDPR Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal(undefined); + expect(queryObject.gdpr_consent).to.equal(undefined); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + }); + + it('GDPR: should only include the GDPR keys for GDPR Consent fields with values', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + gdprDataHandlerStub.restore(); + }) + }); + + describe('impressionViewableHandler', function() { + let sandbox; + let triggerPixelSpy; + let eventsEmitSpy; + let logWinningBidNotFoundSpy; + let callBidViewableBidderSpy; + let winningBidsArray; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + eventsEmitSpy = sandbox.spy(events, ['emit']); + callBidViewableBidderSpy = sandbox.spy(adapterManager, ['callBidViewableBidder']); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('matching winning bid is found', function() { + let moduleConfig = { + firePixels: true + }; + winningBidsArray.push(PBJS_WINNING_BID); + bidViewability.impressionViewableHandler(moduleConfig, GPT_SLOT, null); + // fire pixels should be called + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + // adapterManager.callBidViewableBidder is called with required args + 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 + call = eventsEmitSpy.getCall(0); + expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); + expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); + }); + + it('matching winning bid is NOT found', function() { + // fire pixels should NOT be called + expect(triggerPixelSpy.callCount).to.equal(0); + // adapterManager.callBidViewableBidder is NOT called + expect(callBidViewableBidderSpy.callCount).to.equal(0); + // 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/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/bidscubeBidAdapter_spec.js b/test/spec/modules/bidscubeBidAdapter_spec.js new file mode 100644 index 00000000000..26cf19fc310 --- /dev/null +++ b/test/spec/modules/bidscubeBidAdapter_spec.js @@ -0,0 +1,226 @@ +import { expect } from 'chai' +import { spec } from '../../../modules/bidscubeBidAdapter.js' +import { deepStrictEqual, notEqual, ok, strictEqual } from 'assert' + +describe('BidsCubeAdapter', () => { + const bid = { + bidId: '9ec5b177515ee2e5', + bidder: 'bidscube', + params: { + placementId: 0, + traffic: 'banner', + allParams: '{}' + } + } + + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + strictEqual(true, spec.isBidRequestValid(bid)) + }) + + it('Should return false if at least one of parameters is not present', () => { + const b = { ...bid } + delete b.params.placementId + strictEqual(false, spec.isBidRequestValid(b)) + }) + }) + + describe('buildRequests', () => { + const serverRequest = spec.buildRequests([bid]) + + it('Creates a ServerRequest object with method, URL and data', () => { + ok(serverRequest) + ok(serverRequest.method) + ok(serverRequest.url) + ok(serverRequest.data) + }) + + it('Returns POST method', () => { + strictEqual('POST', serverRequest.method) + }) + + it('Returns valid URL', () => { + strictEqual('https://supply.bidscube.com/?c=o&m=multi', serverRequest.url) + }) + + it('Returns valid data if array of bids is valid', () => { + const { data } = serverRequest + strictEqual('object', typeof data) + deepStrictEqual(['deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'], Object.keys(data)) + strictEqual('number', typeof data.deviceWidth) + strictEqual('number', typeof data.deviceHeight) + strictEqual('string', typeof data.language) + strictEqual('string', typeof data.host) + strictEqual('string', typeof data.page) + notEqual(-1, [0, 1].indexOf(data.secure)) + + const placement = data.placements[0] + 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', () => { + const { placements } = spec.buildRequests([]).data + + expect(spec.buildRequests([]).data.placements).to.be.an('array') + strictEqual(0, placements.length) + }) + }) + + describe('interpretResponse', () => { + const validData = [ + { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['test.com'] + } + }] + }, + { + body: [{ + vastUrl: 'bidscube.com', + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['test.com'] + } + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'bidscube.com', + title: 'Test', + image: 'bidscube.com', + creativeId: '2', + impressionTrackers: ['bidscube.com'], + ttl: 120, + cpm: 0.4, + requestId: '9ec5b177515ee2e5', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['test.com'] + } + }] + } + ] + + for (const obj of validData) { + const { mediaType } = obj.body[0] + + it(`Should interpret ${mediaType} response`, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(1, response.length) + + const copy = { ...obj.body[0] } + 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: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'bidscube.com', + title: 'Test', + impressionTrackers: ['bidscube.com'], + ttl: 120, + requestId: '9ec5b177515ee2e5', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of invalidData) { + const { mediaType } = obj.body[0] + + it(`Should return an empty array if invalid ${mediaType} response is passed `, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + } + + it('Should return an empty array if invalid response is passed', () => { + const response = spec.interpretResponse({ + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + }) + + describe('getUserSyncs', () => { + it('Returns valid URL and type', () => { + const expectedResult = [{ type: 'image', url: 'https://supply.bidscube.com/?c=o&m=cookie' }] + deepStrictEqual(expectedResult, spec.getUserSyncs()) + }) + }) +}) diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js new file mode 100644 index 00000000000..500f45e0573 --- /dev/null +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -0,0 +1,403 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; + +const NATIVE_BID_REQUEST = { + code: 'native_example', + 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: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +}; + +const BANNER_BID_REQUEST = { + code: 'banner_example', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000, + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' +} + +const bidRequest = { + refererInfo: { + referer: 'test.com' + } +} + +const VIDEO_BID_REQUEST = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +} + +const BANNER_BID_RESPONSE = { + 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 VIDEO_BID_RESPONSE = { + 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', + vastUrl: 'http://example.vast', + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const NATIVE_BID_RESPONSE = { + 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: 0, 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('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, NATIVE_BID_REQUEST); + delete bid.params; + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], 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://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + + 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([BANNER_BID_REQUEST]); + + 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('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST]); + + 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://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + }); + + 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: [BANNER_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: BANNER_BID_RESPONSE.id, + cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, + width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, + height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: BANNER_BID_RESPONSE.ttl || 1200, + currency: BANNER_BID_RESPONSE.cur || 'USD', + 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 + } + + 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', '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); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: [VIDEO_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: VIDEO_BID_RESPONSE.id, + cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, + width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, + height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: VIDEO_BID_RESPONSE.ttl || 1200, + currency: VIDEO_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, + 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 + } + + 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', 'vastXml', 'vastUrl', 'ttl', 'creativeId', + '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); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: [NATIVE_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: NATIVE_BID_RESPONSE.id, + cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, + width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, + height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: NATIVE_BID_RESPONSE.ttl || 1200, + currency: NATIVE_BID_RESPONSE.cur || 'USD', + netRevenue: true, + 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} + } + + 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', '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); + 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/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js new file mode 100644 index 00000000000..4fbd0978552 --- /dev/null +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -0,0 +1,559 @@ +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 = () => { + 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: 'banner', + 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, + * adm: string, + * id: number, + * category: number, + * type: number + * } +* } + */ +const getConfigCreative = () => { + return { + ad_id: 5648, + adm: '', + price: 1, + currency: 'EUR', + category: 1, + id: 2825, + type: 1, + viewability_duration: 1, + viewability_percent_in_view: 30, + } +} + +const getConfigCreativeVideo = () => { + return { + ad_id: 5648, + price: 1, + currency: 'EUR', + 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 = () => { + return { + bidderRequestId: '164ddfd207e94d', + bidderCode: 'bliink', + bids: [getConfigBid()], + params: { + bids: [getConfigBid()], + }, + refererInfo: { + canonicalUrl: null, + isAmp: false, + numIframes: 0, + reachedTop: true, + referer: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + }, + } +} + +/** + * @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: '' + } +} + +/** + * + * + * + * @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()) + }, + 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()) + }, + want: { + ad: '', + cpm: 0, + currency: 'EUR', + height: 1, + width: 1, + 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()) + }, + want: [] + }, +] + +describe('BLIINK Adapter interpretResponse', function() { + for (const test of testsInterpretResponse) { + 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: { + * 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(), getConfigCreativeVideo()) + }, + want: { + requestId: getConfigBid().bidId, + cpm: 1, + currency: 'EUR', + mediaType: 'video', + width: 1, + height: 1, + creativeId: getConfigCreativeVideo().creativeId, + netRevenue: false, + vastXml: getConfigCreativeVideo().content, + ad: getConfigCreative().adm, + ttl: 3600, + } + } +] + +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()) + }, + want: { + method: 'GET', + url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest().bids[0].params.tagId}`, + params: { + bidderRequestId: getConfigBuildRequest().bidderRequestId, + bidderCode: getConfigBuildRequest().bidderCode, + bids: getConfigBuildRequest().bids, + refererInfo: getConfigBuildRequest().refererInfo + }, + data: { + 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(), { + gdprConsent: { + gdprApplies: true, + consentString: 'XXXX' + }, + })) + }, + want: { + method: 'GET', + url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest().bids[0].params.tagId}`, + params: { + bidderRequestId: getConfigBuildRequest().bidderRequestId, + bidderCode: getConfigBuildRequest().bidderCode, + bids: getConfigBuildRequest().bids, + refererInfo: getConfigBuildRequest().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 73bd4803358..c831ddf6ddf 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -40,6 +40,13 @@ describe('BlueBillywigAdapter', () => { expect(spec.isBidRequestValid(baseValidBid)).to.equal(true); }); + it('should return false when params missing', () => { + const bid = deepClone(baseValidBid); + delete bid.params; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when publicationName is missing', () => { const bid = deepClone(baseValidBid); delete bid.params.publicationName; @@ -184,6 +191,44 @@ describe('BlueBillywigAdapter', () => { bid.mediaTypes[VIDEO].context = 'instream'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should fail if video is specified but is not an object', () => { + const bid = deepClone(baseValidBid); + + bid.params.video = null; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail if rendererSettings is specified but is not an object', () => { + const bid = deepClone(baseValidBid); + + bid.params.rendererSettings = null; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.rendererSettings = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', () => { @@ -510,30 +555,64 @@ describe('BlueBillywigAdapter', () => { expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; }); - it('should set digitrust when present on bid', () => { - const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}; + it('should set imp.0.video.[w|h|placement] by default', () => { + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(768); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(432); + expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(3); + }); + it('should update imp0.video.[w|h] when present in config', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + newBaseValidBidRequests[0].mediaTypes.video.playerSize = [1, 1]; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); - expect(payload).to.have.nested.property('user.ext.digitrust'); - expect(payload.user.ext.digitrust.id).to.equal(digiTrust.data.id); - expect(payload.user.ext.digitrust.keyv).to.equal(digiTrust.data.keyv); + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(1); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(1); }); - it('should not set digitrust when opted out', () => { - const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: true}, producer: 'ABC', version: 2}}; + it('should allow overriding any imp0.video key through params.video', () => { + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].params.video = { + w: 2, + h: 2, + placement: 1, + minduration: 15, + maxduration: 30 + }; + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(2); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(2); + expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(1); + expect(deepAccess(payload, 'imp.0.video.minduration')).to.equal(15); + expect(deepAccess(payload, 'imp.0.video.maxduration')).to.equal(30); + }); + + it('should not allow placing any non-OpenRTB 2.5 keys on imp.0.video through params.video', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + newBaseValidBidRequests[0].params.video = { + 'true': true, + 'testing': 'some', + 123: {}, + '': 'values' + }; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); - expect(deepAccess(payload, 'user.ext.digitrust')).to.be.undefined; + expect(deepAccess(request, 'imp.0.video.true')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.testing')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.123')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.')).to.be.undefined; }); }); describe('interpretResponse', () => { @@ -571,7 +650,7 @@ describe('BlueBillywigAdapter', () => { bidder: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], + sizes: [[640, 480], [630, 360]], transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' }], start: 11585918458869, @@ -681,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); @@ -759,6 +842,101 @@ describe('BlueBillywigAdapter', () => { expect(result.length).to.equal(0); } }); + + it('should take default width and height when w/h not present', () => { + const bidSizesMissing = deepClone(serverResponse); + + delete bidSizesMissing.body.seatbid[0].bid[0].w; + delete bidSizesMissing.body.seatbid[0].bid[0].h; + + const response = bidSizesMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.width')).to.equal(768); + expect(deepAccess(result, '0.height')).to.equal(432); + }); + + it('should take nurl value when adm not present', () => { + const bidAdmMissing = deepClone(serverResponse); + + delete bidAdmMissing.body.seatbid[0].bid[0].adm; + bidAdmMissing.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; + + const response = bidAdmMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastXml')).to.be.undefined; + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); + }); + + it('should not take nurl value when adm present', () => { + const bidAdmNurlPresent = deepClone(serverResponse); + + bidAdmNurlPresent.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; + + const response = bidAdmNurlPresent; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastXml')).to.equal(bidAdmNurlPresent.body.seatbid[0].bid[0].adm); + expect(deepAccess(result, '0.vastUrl')).to.be.undefined; + }); + + it('should take ext.prebid.cache data when present, ignore ext.prebid.targeting and nurl', () => { + const bidExtPrebidCache = deepClone(serverResponse); + + delete bidExtPrebidCache.body.seatbid[0].bid[0].adm; + bidExtPrebidCache.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; + + bidExtPrebidCache.body.seatbid[0].bid[0].ext = { + prebid: { + cache: { + vastXml: { + url: 'https://bluebillywig.com', + cacheId: '12345' + } + }, + targeting: { + hb_uuid: '23456', + hb_cache_host: 'bluebillywig.com', + hb_cache_path: '/cache' + } + } + }; + + const response = bidExtPrebidCache; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); + expect(deepAccess(result, '0.videoCacheKey')).to.equal('12345'); + }); + + it('should take ext.prebid.targeting data when ext.prebid.cache not present, and ignore nurl', () => { + const bidExtPrebidTargeting = deepClone(serverResponse); + + delete bidExtPrebidTargeting.body.seatbid[0].bid[0].adm; + bidExtPrebidTargeting.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; + + bidExtPrebidTargeting.body.seatbid[0].bid[0].ext = { + prebid: { + targeting: { + hb_uuid: '34567', + hb_cache_host: 'bluebillywig.com', + hb_cache_path: '/cache' + } + } + }; + + const response = bidExtPrebidTargeting; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com/cache?uuid=34567'); + expect(deepAccess(result, '0.videoCacheKey')).to.equal('34567'); + }); }); describe('getUserSyncs', () => { const publicationName = 'bbprebid.dev'; diff --git a/test/spec/modules/bidlabBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js similarity index 70% rename from test/spec/modules/bidlabBidAdapter_spec.js rename to test/spec/modules/boldwinBidAdapter_spec.js index cffd43ae6ca..afb5f935621 100644 --- a/test/spec/modules/bidlabBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -1,13 +1,24 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/bidlabBidAdapter.js'; +import {spec} from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; -describe('BidlabBidAdapter', function () { - let bid = { +describe('BoldwinBidAdapter', function () { + const bid = { bidId: '23fhj33i987f', - bidder: 'bidlab', + bidder: 'boldwin', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, params: { - placementId: 0, - traffic: 'banner' + placementId: 'testBanner', + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' } }; @@ -22,7 +33,7 @@ describe('BidlabBidAdapter', function () { }); 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; @@ -33,7 +44,7 @@ describe('BidlabBidAdapter', 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'); + 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; @@ -45,12 +56,53 @@ describe('BidlabBidAdapter', function () { 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'); - 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.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.adFormat).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let data = serverRequest.data; @@ -78,7 +130,7 @@ describe('BidlabBidAdapter', 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); @@ -88,7 +140,9 @@ describe('BidlabBidAdapter', 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: [{ @@ -108,7 +162,7 @@ describe('BidlabBidAdapter', 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'); @@ -116,6 +170,7 @@ describe('BidlabBidAdapter', 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 = { @@ -139,7 +194,7 @@ describe('BidlabBidAdapter', 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); @@ -152,6 +207,7 @@ describe('BidlabBidAdapter', 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 = { @@ -218,18 +274,15 @@ describe('BidlabBidAdapter', function () { 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'); - } + 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://cs.videowalldirect.com'); }); }); }); 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 644f468abe8..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); @@ -22,6 +30,16 @@ describe('bridgewellBidAdapter', function () { expect(spec.isBidRequestValid(validTag)).to.equal(true); }); + it('should return true when required params found', function () { + const validTag = { + 'bidder': 'bridgewell', + 'params': { + 'cid': 1234 + }, + }; + expect(spec.isBidRequestValid(validTag)).to.equal(true); + }); + it('should return false when required params not found', function () { const invalidTag = { 'bidder': 'bridgewell', @@ -39,6 +57,26 @@ describe('bridgewellBidAdapter', function () { }; expect(spec.isBidRequestValid(invalidTag)).to.equal(false); }); + + it('should return false when required params are empty', function () { + const invalidTag = { + 'bidder': 'bridgewell', + 'params': { + 'cid': '', + }, + }; + expect(spec.isBidRequestValid(invalidTag)).to.equal(false); + }); + + it('should return false when required param cid is not a number', function () { + const invalidTag = { + 'bidder': 'bridgewell', + 'params': { + 'cid': 'bad_cid', + }, + }; + expect(spec.isBidRequestValid(invalidTag)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -57,6 +95,7 @@ describe('bridgewellBidAdapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'userId': userId, }, { 'bidder': 'bridgewell', @@ -96,6 +135,7 @@ describe('bridgewellBidAdapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'userId': userId, } ]; @@ -112,13 +152,66 @@ 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.have.property('adUnitCode').and.to.equal('adunit-code-2'); + 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); + } + }); + + it('should attach valid params to the tag, part2', function() { + const bidderRequest = { + refererInfo: { + referer: 'https://www.bridgewell.com/' + } + } + const bidRequests2 = [ + { + 'bidder': 'bridgewell', + 'params': { + 'cid': 1234, + }, + 'adUnitCode': 'adunit-code-2', + 'mediaTypes': { + 'banner': { + 'sizes': [728, 90] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'userId': userId + }, + ]; + + const request = spec.buildRequests(bidRequests2, bidderRequest); + const payload = request.data; + + expect(payload).to.be.an('object'); + 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++) { + 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); } }); it('should attach validBidRequests to the tag', function () { - const request = spec.buildRequests(bidRequests); + const bidderRequest = { + refererInfo: { + referer: 'https://www.bridgewell.com/' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); const validBidRequests = request.validBidRequests; expect(validBidRequests).to.deep.equal(bidRequests); }); @@ -168,6 +261,7 @@ describe('bridgewellBidAdapter', function () { }, ] }; + const nativeServerResponses = [ { 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d', @@ -175,6 +269,7 @@ describe('bridgewellBidAdapter', function () { 'cpm': 7.0, 'width': 1, 'height': 1, + 'adomain': ['response.com'], 'mediaType': 'native', 'native': { 'image': { @@ -199,6 +294,7 @@ describe('bridgewellBidAdapter', function () { 'currency': 'NTD' }, ]; + const bannerBidRequests = { validBidRequests: [ { @@ -211,6 +307,7 @@ describe('bridgewellBidAdapter', function () { }, ] }; + const bannerServerResponses = [ { 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21', @@ -218,6 +315,7 @@ describe('bridgewellBidAdapter', function () { 'cpm': 5.0, 'width': 300, 'height': 250, + 'adomain': ['response.com'], 'mediaType': 'banner', 'ad': '
test 300x250
', 'ttl': 360, @@ -239,6 +337,7 @@ describe('bridgewellBidAdapter', function () { expect(result[0].currency).to.equal('NTD'); expect(result[0].mediaType).to.equal('native'); expect(result[0].native.image.url).to.equal('https://img.scupio.com/test/test-image.jpg'); + expect(String(result[0].meta.advertiserDomains)).to.equal('response.com'); }); it('should return all required parameters banner', function () { @@ -254,6 +353,7 @@ describe('bridgewellBidAdapter', function () { expect(result[0].currency).to.equal('NTD'); expect(result[0].mediaType).to.equal('banner'); expect(result[0].ad).to.equal('
test 300x250
'); + expect(String(result[0].meta.advertiserDomains)).to.equal('response.com'); }); it('should give up bid if server response is undefiend', function () { diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js new file mode 100644 index 00000000000..5e433abebd8 --- /dev/null +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -0,0 +1,321 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; + +const BIDDER_CODE = 'bmtm'; +const PLACEMENT_ID = 329; + +describe('brightMountainMediaBidAdapter_spec', function () { + let bidBanner = { + bidId: '2dd581a2b6281d', + bidder: BIDDER_CODE, + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: PLACEMENT_ID + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + 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 = { + bidId: '2dd581a2b6281d', + bidder: BIDDER_CODE, + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: PLACEMENT_ID + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + video: { + playerSizes: [[300, 250]], + context: 'outstream', + skip: 0, + playbackmethod: [1, 2], + mimes: ['video/mp4'] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + }; + + let bidderRequest = { + bidderCode: BIDDER_CODE, + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + bids: [bidBanner], + }; + + describe('isBidRequestValid', function () { + it('Should return true when when required params found', function () { + expect(spec.isBidRequestValid(bidBanner)).to.be.true; + }); + it('Should return false when required params are not passed', function () { + bidBanner.params = {}; + expect(spec.isBidRequestValid(bidBanner)).to.be.false; + }); + }); + + 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(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('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('Sends schain info if exists', function () { + expect(data.source.ext).to.be.an('object'); + }); + + 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.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'); + }); + + 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'); + }); + }); + + 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'); + 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.advertiserDomains[0]).to.be.a('string'); + } + }); + + it('Returns an array of valid response if response object is valid for video', function () { + const bidResponse = spec.interpretResponse({ + body: resObjectVideo + }, { bidRequest: bidVideo }); + + 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 () { + const bidResponse = spec.interpretResponse({ + body: '' + }, { bidRequest: bidBanner }); + expect(bidResponse).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + } + it('should return iframe sync option', function () { + expect(spec.getUserSyncs(syncoptionsIframe)).to.be.an('array').with.lengthOf(1); + 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('https://console.brightmountainmedia.com:8443/cookieSync') + }); + }); +}); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index 6477e94d9d6..b7d52c9f7d5 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -141,6 +141,31 @@ describe('brightcomBidAdapter', function() { expect(payload.site.publisher.id).to.equal(1234567); }); + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'brightcom', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + context('when element is fully in view', function() { it('returns 100', function() { Object.assign(element, { width: 600, height: 400 }); @@ -222,7 +247,8 @@ describe('brightcomBidAdapter', function() { 'nurl': '', 'adm': '', 'w': 300, - 'h': 250 + 'h': 250, + 'adomain': ['example.com'] }] }] } @@ -240,7 +266,10 @@ describe('brightcomBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': `
`, - 'ttl': 60 + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } }]; let result = spec.interpretResponse(response); @@ -258,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/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js index 2c6dd234a90..ddb61806006 100644 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -1,5 +1,5 @@ -import { expect } from 'chai'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; +import * as utils from '../../../src/utils.js'; describe('BritePool Submodule', () => { const api_key = '1111'; @@ -16,6 +16,32 @@ describe('BritePool Submodule', () => { }; }; + let triggerPixelStub; + + beforeEach(function (done) { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + done(); + }); + + afterEach(function () { + triggerPixelStub.restore(); + }); + + it('trigger id resolution pixel when no identifiers set', () => { + britepoolIdSubmodule.getId({ params: {} }); + expect(triggerPixelStub.called).to.be.true; + }); + + it('trigger id resolution pixel when no identifiers set with api_key param', () => { + britepoolIdSubmodule.getId({ params: { api_key } }); + expect(triggerPixelStub.called).to.be.true; + }); + + it('does not trigger id resolution pixel when identifiers set', () => { + britepoolIdSubmodule.getId({ params: { api_key, aaid } }); + expect(triggerPixelStub.called).to.be.false; + }); + it('sends x-api-key in header and one identifier', () => { const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); assert(errors.length === 0, errors); @@ -43,12 +69,48 @@ describe('BritePool Submodule', () => { expect(params.url).to.be.undefined; }); + it('test gdpr consent string in url', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: 'expectedConsentString' }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id?gdprString=expectedConsentString'); + }); + + it('test gdpr consent string not in url if gdprApplies false', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: false, consentString: 'expectedConsentString' }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); + }); + + it('test gdpr consent string not in url if consent string undefined', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: undefined }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); + }); + + it('dynamic pub params should be added to params', () => { + window.britepool_pubparams = { ppid: '12345' }; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); + expect(params).to.eql({ aaid, ppid: '12345' }); + window.britepool_pubparams = undefined; + }); + + it('dynamic pub params should override submodule params', () => { + window.britepool_pubparams = { ppid: '67890' }; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, ppid: '12345' }); + expect(params).to.eql({ ppid: '67890' }); + window.britepool_pubparams = undefined; + }); + + it('if dynamic pub params undefined do nothing', () => { + window.britepool_pubparams = undefined; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); + expect(params).to.eql({ aaid }); + window.britepool_pubparams = undefined; + }); + it('test getter override with value', () => { const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_override }); expect(getter).to.equal(getter_override); // Making sure it did not become part of params expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_override }); + const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_override } }); assert.deepEqual(response, { id: { 'primaryBPID': bpid } }); }); @@ -57,7 +119,7 @@ describe('BritePool Submodule', () => { expect(getter).to.equal(getter_callback_override); // Making sure it did not become part of params expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_callback_override }); + const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_callback_override } }); expect(response.callback).to.not.be.undefined; response.callback(result => { assert.deepEqual(result, { 'primaryBPID': bpid }); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js new file mode 100644 index 00000000000..ee37d16905b --- /dev/null +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -0,0 +1,83 @@ +import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; +import {makeSlot} from '../integration/faker/googletag.js'; + +describe('browsi Real time data sub module', function () { + const conf = { + 'auctionDelay': 250, + dataProviders: [{ + 'name': 'browsi', + 'params': { + 'url': 'testUrl.com', + 'siteKey': 'testKey', + 'pubKey': 'testPub', + 'keyName': 'bv' + } + }] + }; + + it('should init and return true', function () { + browsiRTD.collectData(); + expect(browsiRTD.browsiSubmodule.init(conf.dataProviders[0])).to.equal(true) + }); + + it('should create browsi script', function () { + const script = browsiRTD.addBrowsiTag('scriptUrl.com'); + expect(script.getAttribute('data-sitekey')).to.equal('testKey'); + expect(script.getAttribute('data-pubkey')).to.equal('testPub'); + expect(script.async).to.equal(true); + expect(script.prebidData.kn).to.equal(conf.dataProviders[0].params.keyName); + }); + + it('should match placement with ad unit', function () { + const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', 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 test4 = browsiRTD.isIdMatchingAdUnit(slot, []); // true + + expect(test1).to.equal(true); + expect(test2).to.equal(true); + expect(test3).to.equal(false); + expect(test4).to.equal(true); + }); + + it('should return correct macro values', function () { + const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', 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'); + + const macroResultB = browsiRTD.getMacroId({}, slot); + expect(macroResultB).to.equal('browsiAd_1'); + + const macroResultC = browsiRTD.getMacroId({p: '', s: {s: 0, e: 1}}, slot); + expect(macroResultC).to.equal('/'); + }); + + describe('should return data to RTD module', function () { + it('should return empty if no ad units defined', function () { + browsiRTD.setData({}); + expect(browsiRTD.browsiSubmodule.getTargetingData([])).to.eql({}); + }); + + it('should return NA if no prediction for ad unit', function () { + makeSlot({code: 'adMock', divId: 'browsiAd_2'}); + browsiRTD.setData({}); + expect(browsiRTD.browsiSubmodule.getTargetingData(['adMock'])).to.eql({adMock: {bv: 'NA'}}); + }); + + it('should return prediction from server', function () { + makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + const data = { + p: {'hasPrediction': {p: 0.234}}, + kn: 'bv', + pmd: undefined + }; + browsiRTD.setData(data); + expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'])).to.eql({hasPrediction: {bv: '0.20'}}); + }) + }) +}); 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 f14612629b1..d346a14d38a 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -337,7 +337,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - ad: '' + ad: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } }, { requestId: '2e56e1af51a5d8', @@ -348,7 +351,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - vastXml: '' + vastXml: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } } ]; expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); @@ -366,7 +372,10 @@ describe('ccxAdapter', function () { netRevenue: false, ttl: 5, currency: 'PLN', - ad: '' + ad: '', + meta: { + advertiserDomains: ['clickonometrics.com'] + } } ]; expect(spec.interpretResponse({body: response})).to.deep.have.same.members(bidResponses); @@ -425,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/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/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js new file mode 100644 index 00000000000..3755ddc4c4a --- /dev/null +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -0,0 +1,277 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cointrafficBidAdapter.js'; +import { config } from 'src/config.js' +import * as utils from 'src/utils.js' + +const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; + +describe('cointrafficBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250] + ], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345' + }; + + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250] + ], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345' + }, + { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code2', + sizes: [ + [300, 250] + ], + bidId: 'bidId67890"', + bidderRequestId: 'bidderRequestId67890', + auctionId: 'auctionId12345' + } + ]; + + let bidderRequests = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'https://example.com', + stack: [ + 'https://example.com' + ] + } + }; + + it('replaces currency with EUR if there is no currency provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0].data.currency).to.equal('EUR'); + expect(request[1].data.currency).to.equal('EUR'); + }); + + it('replaces currency with EUR if there is no currency provided', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'USD' : 'EUR' + ); + + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0].data.currency).to.equal('USD'); + expect(request[1].data.currency).to.equal('USD'); + + getConfigStub.restore(); + }); + + it('throws an error if currency provided in params is not allowed', function () { + const utilsMock = sinon.mock(utils).expects('logError').twice() + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'BTC' : 'EUR' + ); + + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0]).to.undefined; + expect(request[1]).to.undefined; + + utilsMock.restore() + getConfigStub.restore(); + }); + + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequests); + + 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 () { + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[1].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function () { + it('should get the correct bid response', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'EUR', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = { + body: { + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + mediaType: 'banner', + adomain: ['test.com'] + } + }; + + let expectedResponse = [{ + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + meta: { + mediaType: 'banner', + advertiserDomains: [ + 'test.com', + ] + } + }]; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('should get the correct bid response with different currency', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'USD', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = { + body: { + requestId: 'bidId12345', + cpm: 3.9, + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + mediaType: 'banner', + adomain: ['test.com'] + } + }; + + let expectedResponse = [{ + requestId: 'bidId12345', + cpm: 3.9, + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + meta: { + mediaType: 'banner', + advertiserDomains: [ + 'test.com', + ] + } + }]; + + const getConfigStub = sinon.stub(config, 'getConfig').returns('USD'); + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + + getConfigStub.restore(); + }); + + it('should get empty bid response requested currency is not available', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'BTC', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = {}; + + let expectedResponse = []; + + const getConfigStub = sinon.stub(config, 'getConfig').returns('BTC'); + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + + getConfigStub.restore(); + }); + + it('should get empty bid response if no server response', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'EUR', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = {}; + + let expectedResponse = []; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + }); +}); 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 df9bdcbd47b..fa543f28fd1 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -88,12 +88,13 @@ 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'); + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor'); 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.floor).to.be.an('object'); } }); it('Returns empty data if no valid requests are passed', function () { @@ -108,7 +109,8 @@ describe('ColossussspAdapter', function () { bid.userId.britepoolid = 'britepoolid123'; bid.userId.idl_env = 'idl_env123'; bid.userId.tdid = 'tdid123'; - bid.userId.id5id = 'id5id123' + 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; @@ -118,15 +120,14 @@ 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') - expect(v.uids[0].id).to.be.oneOf(['britepoolid123', 'idl_env123', 'tdid123', 'id5id123']) } } }); @@ -144,7 +145,11 @@ describe('ColossussspAdapter', function () { ttl: 1000, creativeId: '123asd', netRevenue: true, - currency: 'USD' + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } } ] }; let serverResponses = spec.interpretResponse(resObject); @@ -153,7 +158,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 +169,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'); diff --git a/test/spec/modules/concertAnalyticsAdapter_spec.js b/test/spec/modules/concertAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b0aad2f3156 --- /dev/null +++ b/test/spec/modules/concertAnalyticsAdapter_spec.js @@ -0,0 +1,157 @@ +import concertAnalytics from 'modules/concertAnalyticsAdapter.js'; +import { expect } from 'chai'; +const sinon = require('sinon'); +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('ConcertAnalyticsAdapter', function() { + let sandbox; + let xhr; + let requests; + let clock; + let timestamp = 1896134400; + let auctionId = '9f894496-10fe-4652-863d-623462bf82b8'; + let timeout = 1000; + + before(function () { + sandbox = sinon.createSandbox(); + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + + xhr.onCreate = function (request) { + requests.push(request); + }; + clock = sandbox.useFakeTimers(1896134400); + }); + + after(function () { + sandbox.restore(); + }); + + describe('track', function() { + beforeEach(function () { + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.enableAnalytics({ + provider: 'concert' + }); + }); + + afterEach(function () { + events.getEvents.restore(); + concertAnalytics.eventsStorage = []; + concertAnalytics.disableAnalytics(); + }); + + it('should catch all events', function() { + sandbox.spy(concertAnalytics, 'track'); + + fireBidEvents(events); + sandbox.assert.callCount(concertAnalytics.track, 5); + }); + + it('should report data for BID_RESPONSE, BID_WON events', function() { + fireBidEvents(events); + clock.tick(3000 + 1000); + + const eventsToReport = ['bidResponse', 'bidWon']; + for (var i = 0; i < concertAnalytics.eventsStorage.length; i++) { + expect(eventsToReport.indexOf(concertAnalytics.eventsStorage[i].event)).to.be.above(-1); + } + + for (var i = 0; i < eventsToReport.length; i++) { + expect(concertAnalytics.eventsStorage.some(function(event) { + return event.event === eventsToReport[i] + })).to.equal(true); + } + }); + + it('should report data in the shape expected by analytics endpoint', function() { + fireBidEvents(events); + clock.tick(3000 + 1000); + + const requiredFields = ['event', 'concert_rid', 'adId', 'auctionId', 'creativeId', 'position', 'url', 'cpm', 'width', 'height', 'timeToRespond']; + + for (var i = 0; i < requiredFields.length; i++) { + expect(concertAnalytics.eventsStorage[0]).to.have.property(requiredFields[i]); + } + }); + }); + + const adUnits = [{ + code: 'desktop_leaderboard_variable', + sizes: [[1030, 590]], + mediaTypes: { + banner: { + sizes: [[1030, 590]] + } + }, + bids: [ + { + bidder: 'concert', + params: { + partnerId: 'test_partner' + } + } + ] + }]; + + const bidResponse = { + 'bidderCode': 'concert', + 'width': 1030, + 'height': 590, + 'statusMessage': 'Bid available', + 'adId': '642f13fe18ab7dc', + 'requestId': '4062fba2e039919', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 6, + 'ad': '', + 'ttl': 360, + 'creativeId': '138308483085|62bac030-a5d3-11ea-b3be-55590c8153a5', + 'netRevenue': false, + 'currency': 'USD', + 'originalCpm': 6, + 'originalCurrency': 'USD', + 'auctionId': '9f894496-10fe-4652-863d-623462bf82b8', + 'responseTimestamp': 1591213790366, + 'requestTimestamp': 1591213790017, + 'bidder': 'concert', + 'adUnitCode': 'desktop_leaderboard_variable', + 'timeToRespond': 349, + 'status': 'rendered', + 'params': [ + { + 'partnerId': 'cst' + } + ] + } + + const bidWon = { + 'adId': '642f13fe18ab7dc', + 'mediaType': 'banner', + 'requestId': '4062fba2e039919', + 'cpm': 6, + 'creativeId': '138308483085|62bac030-a5d3-11ea-b3be-55590c8153a5', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + 'auctionId': '9f894496-10fe-4652-863d-623462bf82b8', + 'statusMessage': 'Bid available', + 'responseTimestamp': 1591213790366, + 'requestTimestamp': 1591213790017, + 'bidder': 'concert', + 'adUnitCode': 'desktop_leaderboard_variable', + 'sizes': [[1030, 590]], + 'size': [1030, 590] + } + + function fireBidEvents(events) { + events.emit(constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}); + events.emit(constants.EVENTS.BID_REQUESTED, {bidder: 'concert'}); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_WON, bidWon); + } +}); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js new file mode 100644 index 00000000000..1b869d51bde --- /dev/null +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -0,0 +1,219 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec } from 'modules/concertBidAdapter.js'; +import { getStorageManager } from '../../../src/storageManager.js' + +describe('ConcertAdapter', function () { + let bidRequests; + let bidRequest; + let bidResponse; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'concert', + params: { + partnerId: 'foo', + slotType: 'fizz' + }, + adUnitCode: 'desktop_leaderboard_variable', + bidId: 'foo', + transactionId: '', + sizes: [[1030, 590]] + } + ]; + + bidRequest = { + refererInfo: { + referer: 'https://www.google.com' + }, + uspConsent: '1YYY', + gdprConsent: {} + }; + + bidResponse = { + body: { + bids: [ + { + bidId: '16d2e73faea32d9', + cpm: '6', + width: '1030', + height: '590', + ad: '', + ttl: '360', + creativeId: '123349|a7d62700-a4bf-11ea-829f-ad3b0b7a9383', + netRevenue: false, + currency: 'USD' + } + ] + } + } + }); + + describe('spec.isBidRequestValid', function() { + it('should return when it recieved all the required params', function() { + const bid = bidRequests[0]; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when partner id is missing', function() { + const bid = { + bidder: 'concert', + params: {} + } + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('spec.buildRequests', function() { + it('should build a payload object with the shape expected by server', function() { + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + expect(payload).to.have.property('meta'); + expect(payload).to.have.property('slots'); + + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; + + metaRequiredFields.forEach(function(field) { + expect(payload.meta).to.have.property(field); + }); + slotsRequiredFields.forEach(function(field) { + expect(payload.slots[0]).to.have.property(field); + }); + }); + + it('should not generate uid if the user has opted out', function() { + const storage = getStorageManager(); + storage.setDataInLocalStorage('c_nap', 'true'); + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + + it('should generate uid if the user has not opted out', function() { + const storage = getStorageManager(); + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.not.equal(false); + }); + + it('should grab uid from local storage if it exists', function() { + const storage = getStorageManager(); + storage.setDataInLocalStorage('c_uid', 'foo'); + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal('foo'); + }); + }); + + describe('spec.interpretResponse', function() { + it('should return bids in the shape expected by prebid', function() { + const bids = spec.interpretResponse(bidResponse, bidRequest); + const requiredFields = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'meta', 'creativeId', 'netRevenue', 'currency']; + + requiredFields.forEach(function(field) { + expect(bids[0]).to.have.property(field); + }); + }); + + it('should return empty bids if there is no response from server', function() { + const bids = spec.interpretResponse({ body: null }, bidRequest); + expect(bids).to.have.lengthOf(0); + }); + + it('should return empty bids if there are no bids from the server', function() { + const bids = spec.interpretResponse({ body: {bids: []} }, bidRequest); + expect(bids).to.have.lengthOf(0); + }); + }); + + describe('spec.getUserSyncs', function() { + it('should not register syncs when iframe is not enabled', function() { + const opts = { + iframeEnabled: false + } + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync).to.have.lengthOf(0); + }); + + it('should not register syncs when the user has opted out', function() { + const opts = { + iframeEnabled: true + }; + const storage = getStorageManager(); + storage.setDataInLocalStorage('c_nap', 'true'); + + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync).to.have.lengthOf(0); + }); + + it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { + const opts = { + iframeEnabled: true + }; + const storage = getStorageManager(); + storage.removeDataFromLocalStorage('c_nap'); + + bidRequest.gdprConsent = { + gdprApplies: true + }; + + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync[0].url).to.have.string('gdpr_applies=1'); + }); + + it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { + const opts = { + iframeEnabled: true + }; + const storage = getStorageManager(); + storage.removeDataFromLocalStorage('c_nap'); + + bidRequest.gdprConsent = { + gdprApplies: false + }; + + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync[0].url).to.have.string('gdpr_applies=0'); + }); + + it('should set gdpr consent param with the user\'s choices on consent', function() { + const opts = { + iframeEnabled: true + }; + const storage = getStorageManager(); + storage.removeDataFromLocalStorage('c_nap'); + + bidRequest.gdprConsent = { + gdprApplies: false, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + }); + + it('should set ccpa consent param with the user\'s choices on consent', function() { + const opts = { + iframeEnabled: true + }; + const storage = getStorageManager(); + storage.removeDataFromLocalStorage('c_nap'); + + bidRequest.gdprConsent = { + gdprApplies: false, + uspConsent: '1YYY' + }; + + const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(sync[0].url).to.have.string('usp_consent=1YY'); + }); + }); +}); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index 626018241c4..657bc432d06 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -14,7 +14,8 @@ describe('ConnectAd Adapter', function () { bidder: 'conntectad', params: { siteId: 123456, - networkId: 123456 + networkId: 123456, + bidfloor: 0.50 }, adUnitCode: '/19968336/header-bid-tag-1', mediaTypes: { @@ -46,8 +47,7 @@ describe('ConnectAd Adapter', function () { bidderRequestId: '1c56ad30b9b8ca8', transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', userId: { - tdid: '123456', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + tdid: '123456' } }]; @@ -84,7 +84,6 @@ describe('ConnectAd Adapter', function () { } }; const isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); }); @@ -154,6 +153,29 @@ describe('ConnectAd Adapter', function () { expect(requestparse.placements[0].networkId).to.equal(123456); }); + it('should process floors module if available', function() { + const floorInfo = { + currency: 'USD', + floor: 5.20 + }; + bidRequests[0].getFloor = () => floorInfo; + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].bidfloor).to.equal(5.20); + }); + + it('should be bidfloor if no floormodule is available', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].bidfloor).to.equal(0.50); + }); + + it('should have 0 bidfloor value', function() { + const request = spec.buildRequests(bidRequestsUserIds, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].bidfloor).to.equal(0); + }); + it('should contain gdpr info', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); @@ -227,7 +249,6 @@ describe('ConnectAd Adapter', function () { const requestparse = JSON.parse(request.data); expect(requestparse.user.ext.eids).to.be.an('array'); expect(requestparse.user.ext.eids[0].uids[0].id).to.equal('123456'); - expect(requestparse.user.ext.digitrust.id).to.equal('DTID'); }); it('should add referer info', function () { @@ -282,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: { @@ -310,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 2e8d7db92b5..7d3cd48a8e4 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -64,6 +64,13 @@ describe('consentManagement', function () { sinon.assert.calledOnce(utils.logWarn); sinon.assert.notCalled(utils.logInfo); }); + + it('should exit consentManagementUsp module if config is "undefined"', function() { + setConsentConfig(undefined); + expect(consentAPI).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logInfo); + }); }); describe('valid setConsentConfig value', function () { @@ -185,7 +192,10 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { + // from prebid 4425 - "the USP (CCPA) api function __uspapi() always responds synchronously, whether or not privacy data is available, while the GDPR CMP may respond asynchronously + // Because the USP API does not wait for a user response, if it was not successfully obtained before the first auction, we should try again to retrieve privacy data before each subsequent auction. + + it('should not bypass CMP and simply use previously stored consentData', function () { let testConsentData = { uspString: '1YY' }; @@ -208,7 +218,7 @@ describe('consentManagement', function () { let consent = uspDataHandler.getConsentData(); expect(didHookReturn).to.be.true; expect(consent).to.equal(testConsentData.uspString); - sinon.assert.notCalled(uspStub); + sinon.assert.called(uspStub); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index c4f6fe70dd1..5e9b0f07f46 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -24,9 +24,8 @@ describe('consentManagement', function () { setConsentConfig({}); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(10000); - expect(allowAuction).to.be.true; expect(gdprScope).to.be.equal(false); - sinon.assert.callCount(utils.logInfo, 4); + sinon.assert.callCount(utils.logInfo, 3); }); it('should exit consent manager if config is not an object', function () { @@ -40,6 +39,12 @@ describe('consentManagement', function () { expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); + + it('should exit consentManagement module if config is "undefined"', function() { + setConsentConfig(undefined); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); }); describe('valid setConsentConfig value', function () { @@ -58,7 +63,10 @@ describe('consentManagement', function () { setConsentConfig(allConfig); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(7500); - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.true; }); @@ -110,7 +118,10 @@ describe('consentManagement', function () { expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(3333); - expect(allowAuction).to.be.equal(false); + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.equal(false); }); }); @@ -164,7 +175,10 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(staticConsentData).to.be.equal(staticConfig.consentData); }); @@ -244,7 +258,10 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.equal(false); expect(staticConsentData).to.be.equal(staticConfig.consentData); }); @@ -423,7 +440,6 @@ describe('consentManagement', function () { setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(consent.consentString).to.equal(tarConsentString); expect(consent.gdprApplies).to.be.true; @@ -521,6 +537,15 @@ describe('consentManagement', function () { // from CMP window postMessage listener. testIFramedPage('with/JSON response', false, 'encoded_consent_data_via_post_message', 1); testIFramedPage('with/String response', true, 'encoded_consent_data_via_post_message', 1); + + it('should contain correct V1 CMP definition', (done) => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { + const nbArguments = window.__cmp.toString().split('\n')[0].split(', ').length; + expect(nbArguments).to.equal(3); + done(); + }, {}); + }); }); describe('v2 CMP workflow for iframe pages:', function () { @@ -546,6 +571,15 @@ describe('consentManagement', function () { testIFramedPage('with/JSON response', false, 'abc12345234', 2); testIFramedPage('with/String response', true, 'abc12345234', 2); + + it('should contain correct v2 CMP definition', (done) => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { + const nbArguments = window.__tcfapi.toString().split('\n')[0].split(', ').length; + expect(nbArguments).to.equal(4); + done(); + }, {}); + }); }); }); @@ -626,7 +660,6 @@ describe('consentManagement', function () { didHookReturn = true; }, {}); let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); @@ -634,7 +667,33 @@ describe('consentManagement', function () { expect(consent.apiVersion).to.equal(2); }); - it('throws an error when processCmpData check failed while config had allowAuction set to false', function () { + it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { + let testConsentData = { + tcString: 'abc12345234', + addtlConsent: 'superduperstring', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.tcString); + expect(consent.addtlConsent).to.equal(testConsentData.addtlConsent); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(2); + }); + + it('throws an error when processCmpData check fails + does not call requestBids callbcack even when allowAuction is true', function () { let testConsentData = {}; let bidsBackHandlerReturn = false; @@ -642,7 +701,7 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConsentConfig(goodConfigWithCancelAuction); + setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { didHookReturn = true; @@ -650,6 +709,7 @@ describe('consentManagement', function () { let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logError); + sinon.assert.notCalled(utils.logWarn); expect(didHookReturn).to.be.false; expect(bidsBackHandlerReturn).to.be.true; expect(consent).to.be.null; @@ -676,34 +736,12 @@ describe('consentManagement', function () { didHookReturn = true; }, {}); let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); - - it('throws a warning + stores consentData + calls callback when processCmpData check failed while config had allowAuction set to true', function () { - let testConsentData = {}; - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData); - }); - - setConsentConfig(goodConfigWithAllowAuction); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); - let consent = gdprDataHandler.getConsentData(); - - sinon.assert.calledOnce(utils.logWarn); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.be.undefined; - expect(consent.gdprApplies).to.be.false; - expect(consent.apiVersion).to.equal(2); - }); }); }); }); 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/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js new file mode 100644 index 00000000000..368ca8d9e3f --- /dev/null +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -0,0 +1,399 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/contentexchangeBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +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: { + adFormat: BANNER + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + 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 () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + 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://eu2.adnetwork.agency/pbjs'); + }); + + 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', + '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.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 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 () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + 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'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + 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 = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + 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', '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(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + 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'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + 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', 'meta'); + 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'); + 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 = { + 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() { + 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 d802cd288ef..e871ab3f9c6 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,7 +1,7 @@ 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'; describe('Conversant adapter tests', function() { const siteId = '108060'; @@ -339,6 +339,7 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('creativeId', '1000'); expect(bid).to.have.property('width', 300); expect(bid).to.have.property('height', 250); + expect(bid.meta.advertiserDomains).to.deep.equal(['https://example.com']); expect(bid).to.have.property('ad', 'markup000'); expect(bid).to.have.property('ttl', 300); expect(bid).to.have.property('netRevenue', true); @@ -483,7 +484,7 @@ describe('Conversant adapter tests', function() { const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'liveramp.com', uids: [{id: '334455', atype: 1}]} + {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} ]); }); }); @@ -587,4 +588,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 index 1993f28e5d5..285fca9690a 100755 --- a/test/spec/modules/cpmstarBidAdapter_spec.js +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -3,6 +3,41 @@ import { spec } from 'modules/cpmstarBidAdapter.js'; import { deepClone } from 'src/utils.js'; import { config } from 'src/config.js'; +const valid_bid_requests = [{ + 'bidder': 'cpmstar', + 'params': { + 'placementId': '57' + }, + 'sizes': [[300, 250]], + 'bidId': 'bidId' +}]; + +const bidderRequest = { + refererInfo: { + referer: 'referer', + reachedTop: false, + } +}; + +const serverResponse = { + body: [{ + creatives: [{ + cpm: 1, + width: 0, + height: 0, + currency: 'USD', + netRevenue: true, + ttl: 1, + creativeid: '1234', + requestid: '11123', + code: 'no idea', + media: 'banner', + } + ], + syncs: [{ type: 'image', url: 'https://server.cpmstar.com/pixel.aspx' }] + }] +}; + describe('Cpmstar Bid Adapter', function () { describe('isBidRequestValid', function () { it('should return true since the bid is valid', @@ -42,23 +77,6 @@ describe('Cpmstar Bid Adapter', function () { }); describe('buildRequests', function () { - const valid_bid_requests = [{ - 'bidder': 'cpmstar', - 'params': { - 'placementId': '57' - }, - 'sizes': [[300, 250]], - 'bidId': 'bidId' - }]; - - const bidderRequest = { - refererInfo: { - referer: 'referer', - reachedTop: false, - } - - }; - it('should produce a valid production request', function () { var requests = spec.buildRequests(valid_bid_requests, bidderRequest); expect(requests[0]).to.have.property('method'); @@ -109,7 +127,30 @@ describe('Cpmstar Bid Adapter', function () { expect(requests[0]).to.have.property('url'); expect(requests[0].url).to.include('tfcd=1'); }); - }) + }); + + it('should produce a request with support for OpenRTB SupplyChain', function () { + var reqs = deepClone(valid_bid_requests); + reqs[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1 + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1 + } + ] + }; + var requests = spec.buildRequests(reqs, bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('&schain=1.0,1!exchange1.com,1234,1,,,!exchange2.com,abcd,1,,,'); + }); describe('interpretResponse', function () { const request = { @@ -117,23 +158,6 @@ describe('Cpmstar Bid Adapter', function () { mediaType: 'BANNER' } }; - const serverResponse = { - body: [{ - creatives: [{ - cpm: 1, - width: 0, - height: 0, - currency: 'USD', - netRevenue: true, - ttl: 1, - creativeid: '1234', - requestid: '11123', - code: 'no idea', - media: 'banner', - } - ], - }] - }; it('should return a valid bidresponse array', function () { var r = spec.interpretResponse(serverResponse, request) @@ -185,4 +209,23 @@ describe('Cpmstar Bid Adapter', function () { expect(spec.interpretResponse(dealServer, request)[0].dealId).to.equal('deal'); }); }); + + describe('getUserSyncs', function () { + var sres = [deepClone(serverResponse)]; + + it('should return a valid pixel sync', function () { + var syncs = spec.getUserSyncs({ pixelEnabled: true }, sres); + expect(syncs.length).equal(1); + expect(syncs[0].type).equal('image'); + expect(syncs[0].url).equal('https://server.cpmstar.com/pixel.aspx'); + }); + + it('should return a valid iframe sync', function () { + sres[0].body[0].syncs[0].type = 'iframe'; + var syncs = spec.getUserSyncs({ iframeEnabled: true }, sres); + expect(syncs.length).equal(1); + expect(syncs[0].type).equal('iframe'); + expect(syncs[0].url).equal('https://server.cpmstar.com/pixel.aspx'); + }); + }); }); diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index ef7dd7c3232..3f4bc977016 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -81,7 +81,7 @@ describe('craftAdapter', function () { it('sends bid request to ENDPOINT via POST', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://gacraft.jp/prebid-v3'); + expect(request.url).to.equal('https://gacraft.jp/prebid-v3/craft-prebid-example'); let data = JSON.parse(request.data); expect(data.tags).to.deep.equals([{ sitekey: 'craft-prebid-example', diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index c5068d11d31..aa995b3c9a0 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', @@ -742,6 +772,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 = [ { @@ -820,14 +888,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +914,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +926,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +935,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { @@ -889,9 +961,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'], }], }, }; @@ -909,9 +983,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 () { @@ -992,6 +1068,7 @@ describe('The Criteo bidding adapter', function () { zoneid: 123, native: { 'products': [{ + 'sendTargetingKeys': false, 'title': 'Product title', 'description': 'Product desc', 'price': '100', @@ -1027,7 +1104,6 @@ describe('The Criteo bidding adapter', function () { native: true, }] }; - config.setConfig({'enableSendAllBids': false}); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); @@ -1036,61 +1112,87 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].mediaType).to.equal(NATIVE); }); - it('should not parse bid response with native when enableSendAllBids is true', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: {} - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', + it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { + const bidderRequest = { }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], params: { zoneId: 123, + publisherSubId: '123', + nativeCallback: function() {} }, - native: true, - }] - }; - config.setConfig({'enableSendAllBids': true}); - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - - it('should not parse bid response with native when enableSendAllBids is not set', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: {} - }], }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', + { + bidder: 'criteo', + adUnitCode: 'bid-456', + transactionId: 'transaction-456', + sizes: [[728, 90]], params: { - zoneId: 123, + zoneId: 456, + publisherSubId: '456', + nativeCallback: function() {} }, - native: true, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); + }, + ]; + + const nativeParamsWithSendTargetingKeys = [ + { + nativeParams: { + image: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + icon: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + clickUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + displayUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyLink: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyIcon: { + sendTargetingKeys: true + }, + } + } + ]; + + utilsMock.expects('logWarn') + .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') + .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); + nativeParamsWithSendTargetingKeys.forEach(nativeParams => { + let transformedBidRequests = {...bidRequests}; + transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; + spec.buildRequests(transformedBidRequests, bidderRequest); + }); + utilsMock.verify(); }); it('should properly parse a bid response with a zoneId passed as a string', function () { @@ -1159,6 +1261,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 aa5807da0da..828b8401af1 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 () { @@ -71,20 +63,24 @@ describe('CriteoId module', function () { }); it('should call user sync url with the right params', function () { - getCookieStub.withArgs('cto_test_cookie').returns('1'); 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`; + 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 = [ @@ -102,16 +98,19 @@ 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; @@ -141,16 +140,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/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js deleted file mode 100644 index d571150dbee..00000000000 --- a/test/spec/modules/dailyhuntBidAdapter_spec.js +++ /dev/null @@ -1,400 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/dailyhuntBidAdapter.js'; - -const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; -const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; - -const _encodeURIComponent = function (a) { - if (!a) { return } - let b = window.encodeURIComponent(a); - b = b.replace(/'/g, '%27'); - return b; -} - -describe('DailyhuntAdapter', function () { - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'dailyhunt', - 'params': { - placement_id: 1, - publisher_id: 1, - partner_name: 'dailyhunt' - } - }; - - 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 = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - describe('buildRequests', function() { - let bidRequests = [ - { - bidder: 'dailyhunt', - params: { - placement_id: 1, - publisher_id: 1, - partner_name: 'dailyhunt', - bidfloor: 0.1, - device: { - ip: '47.9.247.217' - }, - site: { - cat: ['1', '2', '3'] - } - }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - adUnitCode: 'adunit-code', - sizes: [[300, 50]], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; - let nativeBidRequests = [ - { - bidder: 'dailyhunt', - params: { - placement_id: 1, - publisher_id: 1, - partner_name: 'dailyhunt', - }, - nativeParams: { - title: { - required: true, - len: 80 - }, - image: { - required: true, - sizes: [150, 50] - }, - }, - mediaTypes: { - native: { - title: { - required: true - }, - } - }, - adUnitCode: 'adunit-code', - sizes: [[300, 250], [300, 50]], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; - let videoBidRequests = [ - { - bidder: 'dailyhunt', - params: { - placement_id: 1, - publisher_id: 1, - partner_name: 'dailyhunt' - }, - nativeParams: { - video: { - context: 'instream' - } - }, - mediaTypes: { - video: { - context: 'instream' - } - }, - adUnitCode: 'adunit-code', - sizes: [[300, 250], [300, 50]], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; - let bidderRequest = { - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'bidderCode': 'dailyhunt', - 'bids': [ - { - ...bidRequests[0] - } - ], - 'refererInfo': { - 'referer': 'http://m.dailyhunt.in/' - } - }; - let nativeBidderRequest = { - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'bidderCode': 'dailyhunt', - 'bids': [ - { - ...nativeBidRequests[0] - } - ], - 'refererInfo': { - 'referer': 'http://m.dailyhunt.in/' - } - }; - let videoBidderRequest = { - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'bidderCode': 'dailyhunt', - 'bids': [ - { - ...videoBidRequests[0] - } - ], - 'refererInfo': { - 'referer': 'http://m.dailyhunt.in/' - } - }; - - it('sends display bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); - expect(request.method).to.equal('POST'); - }); - - it('sends native bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; - expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); - expect(request.method).to.equal('POST'); - }); - - it('sends video bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(videoBidRequests, videoBidderRequest)[0]; - expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); - expect(request.method).to.equal('POST'); - }); - }); - describe('interpretResponse', function () { - let bidResponses = { - id: 'da32def7-6779-403c-ada7-0b201dbc9744', - seatbid: [ - { - bid: [ - { - id: 'id1', - impid: 'banner-impid', - price: 1.4, - adm: 'adm', - adid: '66658', - crid: 'asd5ddbf014cac993.66466212', - dealid: 'asd5ddbf014cac993.66466212', - w: 300, - h: 250, - nurl: 'winUrl', - ext: { - prebid: { - type: 'banner' - } - } - }, - { - id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', - impid: 'video-impid', - price: 1.4, - nurl: 'winUrl', - adm: 'adm', - adid: '980', - crid: '2394', - w: 300, - h: 250, - ext: { - prebid: { - 'type': 'video' - }, - bidder: { - cacheKey: 'cache_key', - vastUrl: 'vastUrl' - } - } - }, - { - id: '74973faf-cce7-4eff-abd0-b59b8e91ca87', - impid: 'native-impid', - price: 50, - nurl: 'winUrl', - adm: '{"native":{"link":{"url":"url","clicktrackers":[]},"assets":[{"id":1,"required":1,"img":{},"video":{},"data":{},"title":{"text":"TITLE"},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":2,"value":"Lorem Ipsum Lorem Ipsum Lorem Ipsum."},"title":{},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":12,"value":"Install Here"},"title":{},"link":{}},{"id":1,"required":1,"img":{"type":3,"url":"urk","w":990,"h":505},"video":{},"data":{},"title":{},"link":{}}],"imptrackers":[]}}', - adid: '968', - crid: '2370', - w: 300, - h: 250, - ext: { - prebid: { - type: 'native' - }, - bidder: null - } - }, - { - id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', - impid: 'video-outstream-impid', - price: 1.4, - nurl: 'winUrl', - adm: 'adm', - adid: '980', - crid: '2394', - w: 300, - h: 250, - ext: { - prebid: { - 'type': 'video' - }, - bidder: { - cacheKey: 'cache_key', - vastUrl: 'vastUrl' - } - } - }, - ], - seat: 'dailyhunt' - } - ], - ext: { - responsetimemillis: { - dailyhunt: 119 - } - } - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '1', - cpm: 1.4, - creativeId: 'asd5ddbf014cac993.66466212', - width: 300, - height: 250, - ttl: 360, - netRevenue: true, - currency: 'USD', - ad: 'adm', - mediaType: 'banner', - winUrl: 'winUrl' - }, - { - requestId: '2', - cpm: 1.4, - creativeId: '2394', - width: 300, - height: 250, - ttl: 360, - netRevenue: true, - currency: 'USD', - mediaType: 'video', - winUrl: 'winUrl', - videoCacheKey: 'cache_key', - vastUrl: 'vastUrl', - }, - { - requestId: '3', - cpm: 1.4, - creativeId: '2370', - width: 300, - height: 250, - ttl: 360, - netRevenue: true, - currency: 'USD', - mediaType: 'native', - winUrl: 'winUrl', - native: { - clickUrl: 'https%3A%2F%2Fmontu1996.github.io%2F', - clickTrackers: [], - impressionTrackers: [], - javascriptTrackers: [], - title: 'TITLE', - body: 'Lorem Ipsum Lorem Ipsum Lorem Ipsum.', - cta: 'Install Here', - image: { - url: 'url', - height: 505, - width: 990 - } - } - }, - { - requestId: '4', - cpm: 1.4, - creativeId: '2394', - width: 300, - height: 250, - ttl: 360, - netRevenue: true, - currency: 'USD', - mediaType: 'video', - winUrl: 'winUrl', - vastXml: 'adm', - }, - ]; - let bidderRequest = { - bids: [ - { - bidId: 'banner-impid', - adUnitCode: 'code1', - requestId: '1' - }, - { - bidId: 'video-impid', - adUnitCode: 'code2', - requestId: '2', - mediaTypes: { - video: { - context: 'instream' - } - } - }, - { - bidId: 'native-impid', - adUnitCode: 'code3', - requestId: '3' - }, - { - bidId: 'video-outstream-impid', - adUnitCode: 'code4', - requestId: '4', - mediaTypes: { - video: { - context: 'outstream' - } - } - }, - ] - } - let result = spec.interpretResponse({ body: bidResponses }, bidderRequest); - result.forEach((r, i) => { - expect(Object.keys(r)).to.have.members(Object.keys(expectedResponse[i])); - }); - }); - }) - describe('onBidWon', function () { - it('should hit win url when bid won', function () { - let bid = { - requestId: '1', - cpm: 1.4, - creativeId: 'asd5ddbf014cac993.66466212', - width: 300, - height: 250, - ttl: 360, - netRevenue: true, - currency: 'USD', - ad: 'adm', - mediaType: 'banner', - winUrl: 'winUrl' - }; - expect(spec.onBidWon(bid)).to.equal(undefined); - }); - }) -}) diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 18b8aac7371..0ec12905430 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,41 +81,18 @@ 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, @@ -123,208 +103,423 @@ const bidderRequest = { 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://7560.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/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index 6d9b883e2bb..fcf7056fb3f 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -202,6 +202,7 @@ 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].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); diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js new file mode 100644 index 00000000000..7ea5553393c --- /dev/null +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { config } from 'src/config.js'; + +const DI_COOKIE_NAME = '_dpes_id'; +const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; +const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; + +const cookieConfig = { + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 28 + } +}; + +const html5Config = { + name: 'deepintentId', + storage: { + type: 'html5', + name: '_dpes_id', + expires: 28 + } +} + +describe('Deepintent DPES System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('Deepintent Dpes Sytsem: test "getId" method', () => { + it('Wrong config should fail the tests', () => { + // no config + expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); + + expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + }); + + it('Get value stored in cookie for getId', () => { + getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); + expect(diId).to.deep.equal(DI_COOKIE_OBJECT); + }); + + it('provides the stored deepintentId if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); + expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + }); + }); + + describe('Deepintent Dpes System : test "decode" method', () => { + it('Get the correct decoded value for dpes id', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + }); + }); +}); diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index cd412530d2b..eaffca01e06 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -7,6 +7,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { targeting } from 'src/targeting.js'; import { auctionManager } from 'src/auctionManager.js'; +import { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import * as adpod from 'modules/adpod.js'; import { server } from 'test/mocks/xhr.js'; @@ -38,7 +39,7 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('env', 'vp'); expect(queryParams).to.have.property('gdfp_req', '1'); expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'xml_vast3'); + expect(queryParams).to.have.property('output', 'vast'); expect(queryParams).to.have.property('sz', '640x480'); expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); @@ -115,6 +116,116 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); + it('should include the us_privacy key when USP Consent is available', function () { + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.us_privacy).to.equal('1YYY'); + uspDataHandlerStub.restore(); + }); + + it('should not include the us_privacy key when USP Consent is not available', function () { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.us_privacy).to.equal(undefined); + }); + + it('should include the GDPR keys when GDPR Consent is available', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal('moreConsent'); + gdprDataHandlerStub.restore(); + }); + + it('should not include the GDPR keys when GDPR Consent is not available', function () { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal(undefined); + expect(queryObject.gdpr_consent).to.equal(undefined); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + + it('should only include the GDPR keys for GDPR Consent fields with values', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + }); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal(undefined); + gdprDataHandlerStub.restore(); + }); + describe('special targeting unit test', function () { const allTargetingData = { 'hb_format': 'video', @@ -350,6 +461,14 @@ describe('The DFP video support module', function () { it('should return masterTag url', function() { amStub.returns(getBidsReceived()); + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); let url; parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', @@ -375,15 +494,21 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('env', 'vp'); expect(queryParams).to.have.property('gdfp_req', '1'); expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'xml_vast3'); + expect(queryParams).to.have.property('output', 'vast'); expect(queryParams).to.have.property('sz', '640x480'); expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); + expect(queryParams).to.have.property('us_privacy', '1YYY'); + expect(queryParams).to.have.property('gdpr', '1'); + expect(queryParams).to.have.property('gdpr_consent', 'consent'); + expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); + uspDataHandlerStub.restore(); + gdprDataHandlerStub.restore(); } }); diff --git a/test/spec/modules/dgkeywordRtdProvider_spec.js b/test/spec/modules/dgkeywordRtdProvider_spec.js new file mode 100644 index 00000000000..a145f429557 --- /dev/null +++ b/test/spec/modules/dgkeywordRtdProvider_spec.js @@ -0,0 +1,351 @@ +import * as dgRtd from 'modules/dgkeywordRtdProvider.js'; +import { cloneDeep } from 'lodash'; +import { server } from 'test/mocks/xhr.js'; +import { config } from 'src/config.js'; + +const DG_GET_KEYWORDS_TIMEOUT = 1950; +const IGNORE_SET_ORTB2 = true; +const DEF_CONFIG = { + name: 'dgkeyword', + waitForIt: true, + params: { + timeout: DG_GET_KEYWORDS_TIMEOUT, + }, +}; +const DUMMY_RESPONSE_HEADER = { 'Content-Type': 'application/json' }; +const DUMMY_RESPONSE = { s: ['s1', 's2'], t: ['t1', 't2'] }; +const SUCCESS_RESULT = { opeaud: ['s1', 's2'], opectx: ['t1', 't2'] }; +const SUCCESS_ORTB2 = { + ortb2: { + site: { keywords: SUCCESS_RESULT }, + user: { keywords: SUCCESS_RESULT }, + }, +}; + +describe('Digital Garage Keyword Module', function () { + it('should init and return always true', function () { + expect(dgRtd.dgkeywordSubmodule.init()).to.equal(true); + }); + + describe('dgkeyword target test', function () { + it('should have no target', function () { + const adUnits_no_target = [ + { + code: 'code1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999999, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999998, + dgkeyword: false, + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999997, + }, + }, + ], + }, + { + code: 'code2', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999996, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999995, + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999994, + }, + }, + ], + }, + ]; + expect(dgRtd.getTargetBidderOfDgKeywords(adUnits_no_target)).an('array') + .that.is.empty; + }); + it('should have targets', function () { + const adUnits_targets = [ + { + code: 'code1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999999, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999998, + dgkeyword: true, + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999997, + dgkeyword: false, + }, + }, + ], + }, + { + code: 'code2', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999996, + dgkeyword: true, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999995, + dgkeyword: 'aa', + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999994, + dgkeyword: true, + }, + }, + ], + }, + ]; + const targets = dgRtd.getTargetBidderOfDgKeywords(adUnits_targets); + expect(targets[0].bidder).to.be.equal('dg2'); + expect(targets[0].params.placementId).to.be.equal(99999998); + expect(targets[0].params.dgkeyword).to.be.an('undefined'); + expect(targets[1].bidder).to.be.equal('dg'); + expect(targets[1].params.placementId).to.be.equal(99999996); + expect(targets[1].params.dgkeyword).to.be.an('undefined'); + expect(targets[2].bidder).to.be.equal('dg3'); + expect(targets[2].params.placementId).to.be.equal(99999994); + expect(targets[2].params.dgkeyword).to.be.an('undefined'); + }); + }); + + describe('get profile.', function () { + const AD_UNITS = [ + { + code: 'code1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999999, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999998, + dgkeyword: true, + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999997, + dgkeyword: false, + }, + }, + ], + }, + { + code: 'code2', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'dg', + params: { + placementId: 99999996, + dgkeyword: true, + }, + }, + { + bidder: 'dg2', + params: { + placementId: 99999995, + dgkeyword: 'aa', + }, + }, + { + bidder: 'dg3', + params: { + placementId: 99999994, + }, + }, + ], + }, + ]; + it('should get profiles error(404).', function (done) { + let pbjs = cloneDeep(config); + pbjs.adUnits = cloneDeep(AD_UNITS); + let moduleConfig = cloneDeep(DEF_CONFIG); + dgRtd.getDgKeywordsAndSet( + pbjs, + () => { + let targets = pbjs.adUnits[0].bids; + expect(targets[1].bidder).to.be.equal('dg2'); + expect(targets[1].params.placementId).to.be.equal(99999998); + expect(targets[1].params.dgkeyword).to.be.an('undefined'); + expect(targets[1].params.keywords).to.be.an('undefined'); + targets = pbjs.adUnits[1].bids; + expect(targets[0].bidder).to.be.equal('dg'); + expect(targets[0].params.placementId).to.be.equal(99999996); + expect(targets[0].params.dgkeyword).to.be.an('undefined'); + expect(targets[0].params.keywords).to.be.an('undefined'); + expect(targets[2].bidder).to.be.equal('dg3'); + expect(targets[2].params.placementId).to.be.equal(99999994); + expect(targets[2].params.dgkeyword).to.be.an('undefined'); + expect(targets[2].params.keywords).to.be.an('undefined'); + + expect(pbjs.getBidderConfig()).to.be.deep.equal({}); + + done(); + }, + moduleConfig, + null + ); + const request = server.requests[0]; + request.respond(404); + }); + it('should get profiles timeout.', function (done) { + let pbjs = cloneDeep(config); + pbjs.adUnits = cloneDeep(AD_UNITS); + let moduleConfig = cloneDeep(DEF_CONFIG); + moduleConfig.params.timeout = 10; + dgRtd.getDgKeywordsAndSet( + pbjs, + () => { + let targets = pbjs.adUnits[0].bids; + expect(targets[1].bidder).to.be.equal('dg2'); + expect(targets[1].params.placementId).to.be.equal(99999998); + expect(targets[1].params.dgkeyword).to.be.an('undefined'); + expect(targets[1].params.keywords).to.be.an('undefined'); + targets = pbjs.adUnits[1].bids; + expect(targets[0].bidder).to.be.equal('dg'); + expect(targets[0].params.placementId).to.be.equal(99999996); + expect(targets[0].params.dgkeyword).to.be.an('undefined'); + expect(targets[0].params.keywords).to.be.an('undefined'); + expect(targets[2].bidder).to.be.equal('dg3'); + expect(targets[2].params.placementId).to.be.equal(99999994); + expect(targets[2].params.dgkeyword).to.be.an('undefined'); + expect(targets[2].params.keywords).to.be.an('undefined'); + + expect(pbjs.getBidderConfig()).to.be.deep.equal({}); + + done(); + }, + moduleConfig, + null + ); + setTimeout(() => { + const request = server.requests[0]; + if (request) { + request.respond( + 200, + DUMMY_RESPONSE_HEADER, + JSON.stringify(DUMMY_RESPONSE) + ); + } + }, 1000); + }); + it('should get profiles ok(200).', function (done) { + let pbjs = cloneDeep(config); + pbjs.adUnits = cloneDeep(AD_UNITS); + if (IGNORE_SET_ORTB2) { + pbjs._ignoreSetOrtb2 = true; + } + let moduleConfig = cloneDeep(DEF_CONFIG); + dgRtd.getDgKeywordsAndSet( + pbjs, + () => { + let targets = pbjs.adUnits[0].bids; + expect(targets[1].bidder).to.be.equal('dg2'); + expect(targets[1].params.placementId).to.be.equal(99999998); + expect(targets[1].params.dgkeyword).to.be.an('undefined'); + expect(targets[1].params.keywords).to.be.deep.equal(SUCCESS_RESULT); + targets = pbjs.adUnits[1].bids; + expect(targets[0].bidder).to.be.equal('dg'); + expect(targets[0].params.placementId).to.be.equal(99999996); + expect(targets[0].params.dgkeyword).to.be.an('undefined'); + expect(targets[0].params.keywords).to.be.deep.equal(SUCCESS_RESULT); + expect(targets[2].bidder).to.be.equal('dg3'); + expect(targets[2].params.placementId).to.be.equal(99999994); + expect(targets[2].params.dgkeyword).to.be.an('undefined'); + expect(targets[2].params.keywords).to.be.an('undefined'); + + if (!IGNORE_SET_ORTB2) { + expect(pbjs.getBidderConfig()).to.be.deep.equal({ + dg2: SUCCESS_ORTB2, + dg: SUCCESS_ORTB2, + }); + } + done(); + }, + moduleConfig, + null + ); + const request = server.requests[0]; + request.respond( + 200, + DUMMY_RESPONSE_HEADER, + JSON.stringify(DUMMY_RESPONSE) + ); + }); + }); +}); diff --git a/test/spec/modules/digitrustIdSystem_spec.js b/test/spec/modules/digitrustIdSystem_spec.js deleted file mode 100644 index befd6eb75b6..00000000000 --- a/test/spec/modules/digitrustIdSystem_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { - digiTrustIdSubmodule, - surfaceTestHook -} from 'modules/digiTrustIdSystem.js'; - -let assert = require('chai').assert; -let expect = require('chai').expect; -var testHook = null; - -/** -* A mock implementation of IAB Consent Provider -*/ -function mockCmp(command, version, callback, parameter) { - var resultVal; - if (command == 'ping') { - resultVal = { - gdprAppliesGlobally: mockCmp.stubSettings.isGlobal - }; - callback(resultVal); - } else if (command == 'getVendorConsents') { - let cbResult = { - vendorConsents: [] - } - cbResult.vendorConsents[version] = mockCmp.stubSettings.consents; - callback(cbResult); - } -} - -mockCmp.stubSettings = { - isGlobal: false, - consents: true -}; - -function setupCmpMock(isGlobal, consents) { - window.__cmp = mockCmp; - mockCmp.stubSettings.isGlobal = isGlobal; - mockCmp.stubSettings.consents = consents; -} - -describe('DigiTrust Id System', function () { - it('Should create the test hook', function (done) { - testHook = surfaceTestHook(); - assert.isNotNull(testHook, 'The test hook failed to surface'); - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - } - }; - testHook.initDigitrustFacade(conf); - window.DigiTrust.getUser(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - done(); - }); - - it('Should report as client', function (done) { - delete window.DigiTrust; - testHook = surfaceTestHook(); - - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - expect(window.DigiTrust).to.exist; - expect(result).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - } - }; - testHook.initDigitrustFacade(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isClient).to.be.true; - done(); - }); - - it('Should allow consent when given', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(true, true); - var handler = function(result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - - it('Should consent if does not apply', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(false, true); - var handler = function (result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - - it('Should not allow consent when not given', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(true, false); - var handler = function (result) { - expect(result).to.be.false; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - it('Should deny consent if timeout', function (done) { - window.__cmp = function () { }; - var handler = function (result) { - expect(result).to.be.false; - done(); - } - - testHook.gdpr.hasConsent({ consentTimeout: 1 }, handler); - }); - it('Should pass consent test if cmp not present', function (done) { - delete window.__cmp - testHook = surfaceTestHook(); - var handler = function (result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); -}); diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index d8f0beb9a36..4c060b1f5a4 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -1,6 +1,60 @@ -import {expect} from 'chai'; +import { expect } from 'chai'; import * as _ from 'lodash'; -import {spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle} from '../../../modules/districtmDMXBidAdapter.js'; +import { spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle, getApi, bindUserId, getPlaybackmethod, getProtocols, cleanVast } from '../../../modules/districtmDMXBidAdapter.js'; + +const sample_vast = ` + + + + + + + + + 00:00:15 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` const supportedSize = [ { @@ -42,6 +96,29 @@ const bidRequest = [{ 'dmxid': 100001, 'memberid': 100003, }, + 'userId': { + idl_env: {}, + digitrustid: { + data: { + id: {} + } + }, + id5id: { + uid: '' + }, + pubcid: {}, + tdid: {}, + criteoId: {}, + britepoolid: {}, + intentiqid: {}, + lotamePanoramaId: {}, + parrableId: {}, + netId: {}, + lipb: { + lipbid: {} + }, + + }, 'adUnitCode': 'div-gpt-ad-12345678-1', 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', 'sizes': [ @@ -53,6 +130,35 @@ const bidRequest = [{ 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' }]; +const bidRequestVideo = [{ + 'bidder': 'districtmDMX', + 'params': { + 'dmxid': 100001, + 'memberid': 100003, + 'video': { + id: 123, + skipppable: true, + playback_method: ['auto_play_sound_off', 'viewport_sound_off'], + mimes: ['application/javascript', + 'video/mp4'], + } + }, + 'mediaTypes': { + video: { + context: 'instream', // or 'outstream' + playerSize: [[640, 480]] + } + }, + 'adUnitCode': 'div-gpt-ad-12345678-1', + 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '29a28a1bbc8a8d', + 'bidderRequestId': '124b579a136515', + 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' +}]; const bidRequestNoCoppa = [{ 'bidder': 'districtmDMX', 'params': { @@ -499,12 +605,57 @@ const emptyResponseSeatBid = { body: { seatbid: [] } }; describe('DistrictM Adaptor', function () { const districtm = spec; - describe('verification of upto5', function() { - it('upto5 function should always break 12 imps into 3 request same for 15', function() { + describe('verification of upto5', function () { + it('upto5 function should always break 12 imps into 3 request same for 15', function () { expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) }) }) + + describe('test vast tag', function () { + it('img tag should not be present', function () { + expect(cleanVast(sample_vast).indexOf('img') !== -1).to.be.equal(false) + }) + }) + describe('Test getApi function', function () { + const data = { + api: [1] + } + it('Will return 1 for vpaid version 1', function () { + expect(getApi(data)[0]).to.be.equal(1) + }) + it('Will return 2 for vpaid default', function () { + expect(getApi({})[0]).to.be.equal(2) + }) + }) + + describe('Test cleanSizes function', function () { + it('sequence will be respected', function () { + expect(cleanSizes(bidderRequest.bids[0].sizes).toString()).to.be.equal('300,250,300,600') + }) + it('sequence will be respected', function () { + expect(cleanSizes([[728, 90], [970, 90], [300, 600], [320, 50]]).toString()).to.be.equal('728,90,320,50,300,600,970,90') + }) + }) + + describe('Test getPlaybackmethod function', function () { + it('getPlaybackmethod will return 2', function () { + expect(getPlaybackmethod([])[0]).to.be.equal(2) + }) + it('getPlaybackmethod will return 6', function () { + expect(getPlaybackmethod(['viewport_sound_off'])[0]).to.be.equal(6) + }) + }) + + describe('Test getProtocols function', function () { + it('getProtocols will return 3', function () { + expect(getProtocols({ protocols: [3] })[0]).to.be.equal(3) + }) + it('getProtocols will return 6', function () { + expect(_.isEqual(getProtocols({}), [2, 3, 5, 6, 7, 8])).to.be.equal(true) + }) + }) + describe('All needed functions are available', function () { it(`isBidRequestValid is present and type function`, function () { expect(districtm.isBidRequestValid).to.exist.and.to.be.a('function') @@ -535,17 +686,16 @@ describe('DistrictM Adaptor', function () { describe(`isBidRequestValid test response`, function () { let params = { - dmxid: 10001, + dmxid: 10001, // optional memberid: 10003, }; it(`function should return true`, function () { - expect(districtm.isBidRequestValid({params})).to.be.equal(true); + expect(districtm.isBidRequestValid({ params })).to.be.equal(true); }); it(`function should return false`, function () { - expect(districtm.isBidRequestValid({ params: { memberid: 12345 } })).to.be.equal(false); + expect(districtm.isBidRequestValid({ params: {} })).to.be.equal(false); }); - it(`expect to have two property available dmxid and memberid`, function () { - expect(params).to.have.property('dmxid'); + it(`expect to have memberid`, function () { expect(params).to.have.property('memberid'); }); }); @@ -568,19 +718,19 @@ describe('DistrictM Adaptor', function () { it(`the function should return an array`, function () { expect(buildRequestResults).to.be.an('object'); }); - it(`contain gdpr consent & ccpa`, function() { + it(`contain gdpr consent & ccpa`, function () { const bidr = JSON.parse(buildRequestResults.data) expect(bidr.regs.ext.gdpr).to.be.equal(1); expect(bidr.regs.ext.us_privacy).to.be.equal('1NY'); expect(bidr.user.ext.consent).to.be.an('string'); }); - it(`test contain COPPA`, function() { + it(`test contain COPPA`, function () { const bidr = JSON.parse(buildRequestResults.data) bidr.regs = bidr.regs || {}; bidr.regs.coppa = 1; expect(bidr.regs.coppa).to.be.equal(1) }) - it(`test should not contain COPPA`, function() { + it(`test should not contain COPPA`, function () { const bidr = JSON.parse(buildRequestResultsNoCoppa.data) expect(bidr.regs.coppa).to.be.equal(0) }) @@ -589,11 +739,17 @@ describe('DistrictM Adaptor', function () { }); }); + describe('bidRequest Video testing', function () { + const request = districtm.buildRequests(bidRequestVideo, bidRequestVideo); + const data = JSON.parse(request.data) + expect(data instanceof Object).to.be.equal(true) + }) + describe(`interpretResponse test usage`, function () { - const responseResults = districtm.interpretResponse(responses, {bidderRequest}); - const emptyResponseResults = districtm.interpretResponse(emptyResponse, {bidderRequest}); - const emptyResponseResultsNegation = districtm.interpretResponse(responsesNegative, {bidderRequest}); - const emptyResponseResultsEmptySeat = districtm.interpretResponse(emptyResponseSeatBid, {bidderRequest}); + const responseResults = districtm.interpretResponse(responses, { bidderRequest }); + const emptyResponseResults = districtm.interpretResponse(emptyResponse, { bidderRequest }); + const emptyResponseResultsNegation = districtm.interpretResponse(responsesNegative, { bidderRequest }); + const emptyResponseResultsEmptySeat = districtm.interpretResponse(emptyResponseSeatBid, { bidderRequest }); it(`the function should return an array`, function () { expect(responseResults).to.be.an('array'); }); @@ -613,10 +769,10 @@ describe('DistrictM Adaptor', function () { }); describe(`check validation for id sync gdpr ccpa`, () => { - let allin = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] - let noCCPA = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, null)[0] - let noGDPR = spec.getUserSyncs({iframeEnabled: true}, {}, null, bidderRequest.uspConsent)[0] - let nothing = spec.getUserSyncs({iframeEnabled: true}, {}, null, null)[0] + let allin = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] + let noCCPA = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, null)[0] + let noGDPR = spec.getUserSyncs({ iframeEnabled: true }, {}, null, bidderRequest.uspConsent)[0] + let nothing = spec.getUserSyncs({ iframeEnabled: true }, {}, null, null)[0] /* @@ -639,10 +795,10 @@ describe('DistrictM Adaptor', function () { }) describe(`Helper function testing`, function () { - const bid = matchRequest('29a28a1bbc8a8d', {bidderRequest}); - const {width, height} = defaultSize(bid); + const bid = matchRequest('29a28a1bbc8a8d', { bidderRequest }); + const { width, height } = defaultSize(bid); it(`test matchRequest`, function () { - expect(matchRequest('29a28a1bbc8a8d', {bidderRequest})).to.be.an('object'); + expect(matchRequest('29a28a1bbc8a8d', { bidderRequest })).to.be.an('object'); }); it(`test checkDeepArray`, function () { expect(_.isEqual(checkDeepArray([728, 90]), [728, 90])).to.be.equal(true); diff --git a/test/spec/modules/djaxBidAdapter_spec.js b/test/spec/modules/djaxBidAdapter_spec.js deleted file mode 100644 index bef2b1fddc5..00000000000 --- a/test/spec/modules/djaxBidAdapter_spec.js +++ /dev/null @@ -1,159 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/djaxBidAdapter.js'; - -const ENDPOINT = 'https://demo.reviveadservermod.com/headerbidding_adminshare/www/admin/plugins/Prebid/getAd.php'; - -describe('The Djax bidding adapter', function () { - describe('isBidRequestValid', function () { - it('should return false when given an invalid bid', function () { - const bid = { - bidder: 'djax', - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }); - - it('should return true when given a publisherId in bid', function () { - const bid = { - bidder: 'djax', - params: { - publisherId: 2 - }, - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [{ - 'bidder': 'djax', - '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': 'djax', - 'params': { - 'publisherId': 2 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [300, 600] - ] - } - }, - 'bidId': '2ee937f15015c6', - 'src': 'client', - }, - { - 'bidder': 'djax', - '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/dmdIdSystem_spec.js b/test/spec/modules/dmdIdSystem_spec.js new file mode 100644 index 00000000000..3096a8e55f5 --- /dev/null +++ b/test/spec/modules/dmdIdSystem_spec.js @@ -0,0 +1,96 @@ +import * as utils from '../../../src/utils.js'; +import { server } from 'test/mocks/xhr.js'; +import { dmdIdSubmodule } from 'modules/dmdIdSystem.js'; + +describe('Dmd ID System', function () { + let logErrorStub; + const config = { + params: { + api_key: '33344ffjddk22k22k222k22234k', + api_url: 'https://aix.hcn.health/api/v1/auths' + } + }; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if no configParams were passed into getId', function () { + dmdIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if configParams doesnot have api_key passed to getId', function () { + dmdIdSubmodule.getId({params: {}}); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if configParams has invalid api_key passed into getId', function () { + dmdIdSubmodule.getId({params: {api_key: 123}}); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should not log an error if configParams has valid api_key passed into getId', function () { + dmdIdSubmodule.getId({params: {api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c'}}); + expect(logErrorStub.calledOnce).to.be.false; + }); + + it('should return undefined if empty value passed into decode', function () { + expect(dmdIdSubmodule.decode()).to.be.undefined; + }); + + it('should return undefined if invalid dmd-dgid passed into decode', function () { + expect(dmdIdSubmodule.decode(123)).to.be.undefined; + }); + + it('should return dmdId if valid dmd-dgid passed into decode', function () { + let data = { 'dmdId': 'U12345' }; + expect(dmdIdSubmodule.decode('U12345')).to.deep.equal(data); + }); + + it('should return cacheObj if cacheObj is passed into getId', function () { + let data = { 'dmdId': 'U12345' }; + expect(dmdIdSubmodule.getId(config, {}, { cookie: 'dmd-dgid' })).to.deep.equal({ cookie: 'dmd-dgid' }); + expect(server.requests.length).to.eq(0); + }); + + it('Should invoke callback with response from API call', function () { + const callbackSpy = sinon.spy(); + const domain = utils.getWindowLocation() + const callback = dmdIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.method).to.eq('GET'); + expect(request.requestHeaders['x-domain']).to.be.eq(domain); + expect(request.url).to.eq(config.params.api_url); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ dgid: 'U12345' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal('U12345'); + }); + + it('Should log error if API response is not valid', function () { + const callbackSpy = sinon.spy(); + const domain = utils.getWindowLocation() + const callback = dmdIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.method).to.eq('GET'); + expect(request.requestHeaders['x-domain']).to.be.eq(domain); + expect(request.url).to.eq(config.params.api_url); + request.respond(400, { 'Content-Type': 'application/json' }, undefined); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('Should log error if API call throws error', function () { + const callbackSpy = sinon.spy(); + const callback = dmdIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(config.params.api_url); + request.error(); + expect(logErrorStub.calledOnce).to.be.true; + }); +}); diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js new file mode 100644 index 00000000000..efff2efa319 --- /dev/null +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -0,0 +1,97 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/docereeBidAdapter.js'; +import { config } from '../../../src/config.js'; + +describe('BidlabBidAdapter', function () { + config.setConfig({ + doceree: { + context: { + data: { + token: 'testing-token', // required + } + }, + user: { + data: { + gender: '', + email: '', + hashedEmail: '', + firstName: '', + lastName: '', + npi: '', + hashedNPI: '', + city: '', + zipCode: '', + specialization: '', + } + } + } + }); + let bid = { + bidId: 'testing', + bidder: 'doceree', + params: { + placementId: 'DOC_7jm9j5eqkl0xvc5w', + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if placementId is present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if placementId is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + serverRequest = serverRequest[0] + 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; + }); + it('Returns GET method', function () { + expect(serverRequest.method).to.equal('GET'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&'); + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: { + DIVID: 'DOC_7jm9j5eqkl0xvc5w', + creativeType: 'banner', + guid: 'G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q', + currency: 'USD', + cpmBid: 2, + height: '250', + width: '300', + ctaLink: 'https://doceree.com/', + sourceURL: '', + sourceHTML: '
test
', + advertiserDomain: 'doceree.com', + } + }; + 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', + 'netRevenue', 'currency', 'mediaType', 'creativeId', 'meta'); + expect(dataItem.requestId).to.equal('G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q'); + expect(dataItem.cpm).to.equal(2); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('
test
'); + expect(dataItem.ttl).to.equal(30); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.creativeId).to.equal('DOC_7jm9j5eqkl0xvc5w'); + expect(dataItem.meta.advertiserDomains).to.be.an('array').that.is.not.empty; + expect(dataItem.meta.advertiserDomains[0]).to.equal('doceree.com') + }); + }) +}); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index cf36c3f62c4..09f40895ec9 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -61,7 +61,11 @@ describe('dspxAdapter', function () { ], 'bidId': '30b31c1838de1e1', 'bidderRequestId': '22edbae2733bf61', - 'auctionId': '1d1a030790a475' + 'auctionId': '1d1a030790a475', + 'userId': { + 'netId': '123', + 'uid2': '456' + } }, { 'bidder': 'dspx', @@ -102,9 +106,18 @@ describe('dspxAdapter', function () { 'placement': '101', 'devMode': true }, - 'sizes': [ - [300, 250] - ], + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + }, + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'bidId': '30b31c1838de1e4', 'bidderRequestId': '22edbae2733bf67', 'auctionId': '1d1a030790a478' @@ -145,7 +158,7 @@ describe('dspxAdapter', function () { expect(request1.method).to.equal('GET'); expect(request1.url).to.equal(ENDPOINT_URL); let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&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'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&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&did_netid=123&did_uid2=456'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -199,7 +212,8 @@ describe('dspxAdapter', function () { 'currency': 'EUR', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'adomain': ['bdomain'] } }; let serverVideoResponse = { @@ -229,7 +243,8 @@ describe('dspxAdapter', function () { netRevenue: true, ttl: 300, type: 'sspHTML', - ad: '' + ad: '', + meta: {advertiserDomains: ['bdomain']} }, { requestId: '23beaa6af6cdde', cpm: 0.5, @@ -242,7 +257,8 @@ describe('dspxAdapter', function () { ttl: 300, type: 'vast2', vastXml: '{"reason":7001,"status":"accepted"}', - mediaType: 'video' + mediaType: 'video', + meta: {advertiserDomains: []} }]; it('should get the correct bid response by display ad', function () { @@ -255,6 +271,8 @@ describe('dspxAdapter', function () { }]; let result = spec.interpretResponse(serverResponse, 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 dspx video bid response by display ad', function () { @@ -273,6 +291,7 @@ describe('dspxAdapter', 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 () { @@ -283,4 +302,83 @@ describe('dspxAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); + + describe(`getUserSyncs test usage in passback response`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + reason: 8002, + status: 'error', + msg: 'passback', + } + }]; + }); + + 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/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js index ba1cad475da..1c46381500f 100644 --- a/test/spec/modules/ebdrBidAdapter_spec.js +++ b/test/spec/modules/ebdrBidAdapter_spec.js @@ -160,7 +160,12 @@ describe('ebdrBidAdapter', function () { currency: 'USD', netRevenue: true, ttl: 3600, - vastUrl: serverResponse.seatbid[0].bid[0].nurl + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + meta: { + advertiserDomains: [ + 'advertiserdomain.com' + ] + } }); }); }); @@ -201,7 +206,12 @@ describe('ebdrBidAdapter', function () { height: serverResponse.seatbid[0].bid[0].h, currency: 'USD', netRevenue: true, - ttl: 3600 + ttl: 3600, + meta: { + advertiserDomains: [ + 'advertiserdomain.com' + ] + }, }); }); }); diff --git a/test/spec/modules/edgequeryxBidAdapter_spec.js b/test/spec/modules/edgequeryxBidAdapter_spec.js deleted file mode 100644 index a66c546bd7c..00000000000 --- a/test/spec/modules/edgequeryxBidAdapter_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import { - expect -} from 'chai'; -import { - spec -} from 'modules/edgequeryxBidAdapter.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'; - -// Default params with optional ones -describe('Edge Query X bid adapter tests', function () { - var DEFAULT_PARAMS = [{ - bidId: 'abcd1234', - mediaTypes: { - banner: { - sizes: [ - [1, 1] - ] - } - }, - bidder: 'edgequeryx', - params: { - accountId: 'test', - widgetId: 'test' - }, - requestId: 'efgh5678', - transactionId: 'zsfgzzg' - }]; - var BID_RESPONSE = { - body: { - requestId: 'abcd1234', - cpm: 22, - width: 1, - height: 1, - creativeId: 'EQXTest', - currency: 'EUR', - netRevenue: true, - ttl: 360, - ad: '< --- awesome script --- >' - } - }; - - it('Verify build request', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - } - }); - const request = spec.buildRequests(DEFAULT_PARAMS); - expect(request[0]).to.have.property('url').and.to.equal('https://deep.edgequery.io/prebid/x'); - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.have.property('accountId').and.to.equal('test'); - expect(requestContent).to.have.property('widgetId').and.to.equal('test'); - expect(requestContent).to.have.property('sizes'); - expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(1); - expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(1); - }); - - it('Verify parse response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS); - const bids = spec.interpretResponse(BID_RESPONSE, request[0]); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.cpm).to.equal(22); - expect(bid.ad).to.equal('< --- awesome script --- >'); - expect(bid.width).to.equal(1); - expect(bid.height).to.equal(1); - expect(bid.creativeId).to.equal('EQXTest'); - expect(bid.currency).to.equal('EUR'); - expect(bid.netRevenue).to.equal(true); - expect(bid.ttl).to.equal(360); - expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); - - expect(function () { - spec.interpretResponse(BID_RESPONSE, { - data: 'invalid Json' - }) - }).to.not.throw(); - }); - - it('Verifies bidder code', function () { - expect(spec.code).to.equal('edgequeryx'); - }); - - it('Verifies bidder aliases', function () { - expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('eqx'); - }); - - it('Verifies if bid request valid', function () { - expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ - params: {} - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - widgetyId: 'abcdef' - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - widgetId: 'test', - accountId: 'test' - } - })).to.equal(true); - }); -}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 1cbc2911ef5..1277486a154 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -29,27 +29,73 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('id5Id', function() { + describe('id5Id', function() { + it('does not include an ext if not provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ id: 'some-random-id-value', atype: 1 }] + }); + }); + + it('includes ext if provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value', + ext: { + linkType: 0 + } + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 0 + } + }] + }); + }); + }); + + it('parrableId', function() { const userId = { - id5id: 'some-random-id-value' + parrableId: { + eid: 'some-random-id-value' + } }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'id5-sync.com', + source: 'parrable.com', uids: [{id: 'some-random-id-value', atype: 1}] }); }); - it('parrableId', function() { + it('merkleId', function() { const userId = { - parrableid: 'some-random-id-value' + merkleId: { + id: 'some-random-id-value', keyID: 1 + } }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'parrable.com', - uids: [{id: 'some-random-id-value', atype: 1}] + source: 'merkleinc.com', + uids: [{id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 + }}] }); }); @@ -61,7 +107,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveramp.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -76,7 +122,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}], + uids: [{id: 'some-random-id-value', atype: 3}], ext: {segments: ['s1', 's2']} }); }); @@ -91,7 +137,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -103,7 +149,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -119,34 +165,42 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('DigiTrust; getValue call', function() { + it('criteo', function() { const userId = { - digitrustid: { - data: { - id: 'some-random-id-value' - } - } + criteoId: 'some-random-id-value' }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'digitru.st', + source: 'criteo.com', uids: [{id: 'some-random-id-value', atype: 1}] }); }); - it('criteo', function() { + it('tapadId', function() { const userId = { - criteoId: 'some-random-id-value' + tapadId: 'some-random-id-value' }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'criteo.com', + source: 'tapad.com', uids: [{id: 'some-random-id-value', atype: 1}] }); }); + it('deepintentId', function() { + const userId = { + deepintentId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' @@ -158,44 +212,143 @@ describe('eids array generation for known sub-modules', function() { uids: [{id: 'some-random-id-value', atype: 1}] }); }); - it('Sharedid', function() { + + it('NextRollId', function() { const userId = { - sharedid: { - id: 'test_sharedId', - third: 'test_sharedId' - } + nextrollId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'nextroll.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + + it('zeotapIdPlus', function() { + const userId = { + IDP: 'some-random-id-value' }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'sharedid.org', + source: 'zeotap.com', uids: [{ - id: 'test_sharedId', - atype: 1, - ext: { - third: 'test_sharedId' - } + id: 'some-random-id-value', + atype: 1 }] }); }); - it('Sharedid: Not Synched', function() { + + it('haloId', function() { const userId = { - sharedid: { - id: 'test_sharedId' - } + haloId: 'some-random-id-value' }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'sharedid.org', + source: 'audigent.com', uids: [{ - id: 'test_sharedId', + id: 'some-random-id-value', atype: 1 }] }); }); -}); + it('quantcastId', function() { + const userId = { + quantcastId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'quantcast.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + it('uid2', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); + it('kpuid', function() { + const userId = { + kpuid: 'Sample_Token' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'kpuid.com', + uids: [{ + id: 'Sample_Token', + atype: 3 + }] + }); + }); + it('pubProvidedId', function() { + const userId = { + pubProvidedId: [{ + 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' + }] + }] + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(2); + expect(newEids[0]).to.deep.equal({ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }); + expect(newEids[1]).to.deep.equal({ + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage' + }] + }); + }); + + it('amxId', () => { + const id = 'c4bcadb0-124f-4468-a91a-d3d44cf311c5' + const userId = { + amxId: id + }; + + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'amxrtb.com', + uids: [{ + atype: 1, + id, + }] + }); + }); +}); describe('Negative case', function() { it('eids array generation for UN-known sub-module', function() { // UnknownCommonId diff --git a/test/spec/modules/emoteevBidAdapter_spec.js b/test/spec/modules/emoteevBidAdapter_spec.js deleted file mode 100644 index 43ae62f1eb9..00000000000 --- a/test/spec/modules/emoteevBidAdapter_spec.js +++ /dev/null @@ -1,876 +0,0 @@ -import { - assert, expect -} from 'chai'; -import { - ADAPTER_VERSION, - DOMAIN, - DOMAIN_DEVELOPMENT, - DOMAIN_STAGING, - domain, - BIDDER_PATH, - bidderUrl, - buildRequests, - conformBidRequest, - DEFAULT_ENV, - DEVELOPMENT, - EVENTS_PATH, - eventsUrl, - FOOTER, - gdprConsent, - getDeviceDimensions, - getDeviceInfo, - getDocumentDimensions, - getUserSyncs, - getViewDimensions, - IN_CONTENT, - interpretResponse, - isBidRequestValid, - ON_ADAPTER_CALLED, - ON_BID_WON, - ON_BIDDER_TIMEOUT, - onBidWon, - onAdapterCalled, - onTimeout, - OVERLAY, - PRODUCTION, - requestsPayload, - resolveDebug, - resolveEnv, - spec, - STAGING, - USER_SYNC_IFRAME_PATH, - USER_SYNC_IMAGE_PATH, - userSyncIframeUrl, - userSyncImageUrl, - validateSizes, - validateContext, - validateExternalId, - VENDOR_ID, - WALLPAPER, - storage -} from 'modules/emoteevBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import {config} from '../../../src/config.js'; - -const cannedValidBidRequests = [{ - adUnitCode: '/19968336/header-bid-tag-1', - auctionId: 'fcbf2b27-a951-496f-b5bb-1324ce7c0558', - bidId: '2b8de6572e8193', - bidRequestsCount: 1, - bidder: 'emoteev', - bidderRequestId: '1203b39fecc6a5', - crumbs: {pubcid: 'f3371d16-4e8b-42b5-a770-7e5be1fdf03d'}, - params: { - adSpaceId: 5084, - context: IN_CONTENT, - externalId: 42 - }, - sizes: [[300, 250], [250, 300], [300, 600]], - transactionId: '58dbd732-7a39-45f1-b23e-1c24051a941c', -}]; -const cannedBidderRequest = { - auctionId: 'fcbf2b27-a951-496f-b5bb-1324ce7c0558', - auctionStart: 1544200122837, - bidderCode: 'emoteev', - bidderRequestId: '1203b39fecc6a5', - doneCbCallCount: 0, - refererInfo: { - canonicalUrl: undefined, - numIframes: 0, - reachedTop: true, - referer: 'https://localhost:9999/integrationExamples/gpt/hello_world_emoteev.html', - stack: ['https://localhost:9999/integrationExamples/gpt/hello_world_emoteev.html'] - }, - start: 1544200012839, - timeout: 3000, - gdprConsent: { - gdprApplies: true, - vendorData: {vendorConsents: {[VENDOR_ID]: true}}, - } -}; -const serverResponse = - { - body: [ - { - requestId: cannedValidBidRequests[0].bidId, - cpm: 1, - width: cannedValidBidRequests[0].sizes[0][0], - height: cannedValidBidRequests[0].sizes[0][1], - ad: '
', - ttl: 360, - creativeId: 123, - netRevenue: false, - currency: 'EUR', - } - ] - }; - -describe('emoteevBidAdapter', function () { - describe('isBidRequestValid', function () { - it('should return true when valid', function () { - const validBid = { - bidder: 'emoteev', - bidId: '23a45b4e3', - params: { - adSpaceId: 12345, - context: IN_CONTENT, - externalId: 42 - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - }; - expect(isBidRequestValid(validBid)).to.equal(true); - - expect(spec.isBidRequestValid(validBid)).to.exist.and.to.be.a('boolean'); - expect(spec.isBidRequestValid({})).to.exist.and.to.be.a('boolean'); - }); - - it('should return false when required params are invalid', function () { - expect(isBidRequestValid({ - bidder: '', // invalid bidder - params: { - adSpaceId: 12345, - context: IN_CONTENT, - externalId: 42 - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - })).to.equal(false); - expect(isBidRequestValid({ - bidder: 'emoteev', - params: { - adSpaceId: '', // invalid adSpaceId - context: IN_CONTENT, - externalId: 42 - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - })).to.equal(false); - expect(isBidRequestValid({ - bidder: 'emoteev', - params: { - adSpaceId: 12345, - context: 'something', // invalid context - externalId: 42 - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - })).to.equal(false); - expect(isBidRequestValid({ - bidder: 'emoteev', - params: { - adSpaceId: 12345, - context: IN_CONTENT, - externalId: 'lol' // invalid externalId - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - })).to.equal(false); - expect(isBidRequestValid({ - bidder: 'emoteev', - params: { - adSpaceId: 12345, - context: IN_CONTENT, - externalId: 42 - }, - mediaTypes: { - banner: { - sizes: [[750]] // invalid size - } - }, - })).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const - env = DEFAULT_ENV, - debug = true, - currency = 'EUR', - request = buildRequests(env, debug, currency, cannedValidBidRequests, cannedBidderRequest); - - expect(request).to.exist.and.have.all.keys( - 'method', - 'url', - 'data', - ); - - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(bidderUrl(env)); - - expect(spec.buildRequests(cannedValidBidRequests, cannedBidderRequest)).to.exist.and.to.be.an('object'); - }); - - describe('interpretResponse', function () { - it('bid objects from response', function () { - const bidResponses = interpretResponse(serverResponse); - expect(bidResponses).to.be.an('array').that.is.not.empty; - expect(bidResponses[0]).to.have.property('requestId', cannedValidBidRequests[0].bidId); - expect(bidResponses[0]).to.have.property('cpm', serverResponse.body[0].cpm); - expect(bidResponses[0]).to.have.property('width', serverResponse.body[0].width); - expect(bidResponses[0]).to.have.property('height', serverResponse.body[0].height); - expect(bidResponses[0]).to.have.property('ad', serverResponse.body[0].ad); - expect(bidResponses[0]).to.have.property('ttl', serverResponse.body[0].ttl); - expect(bidResponses[0]).to.have.property('creativeId', serverResponse.body[0].creativeId); - expect(bidResponses[0]).to.have.property('netRevenue', serverResponse.body[0].netRevenue); - expect(bidResponses[0]).to.have.property('currency', serverResponse.body[0].currency); - }); - }); - - describe('onAdapterCalled', function () { - const - bidRequest = cannedValidBidRequests[0], - url = onAdapterCalled(DEFAULT_ENV, bidRequest); - - expect(url).to.have.property('protocol'); - expect(url).to.have.property('hostname'); - expect(url).to.have.property('pathname', EVENTS_PATH); - expect(url).to.have.nested.property('search.eventName', ON_ADAPTER_CALLED); - expect(url).to.have.nested.property('search.pubcId', bidRequest.crumbs.pubcid); - expect(url).to.have.nested.property('search.bidId', bidRequest.bidId); - expect(url).to.have.nested.property('search.adSpaceId', bidRequest.params.adSpaceId); - expect(url).to.have.nested.property('search.cache_buster'); - }); - - describe('onBidWon', function () { - const - pubcId = cannedValidBidRequests[0].crumbs.pubcid, - bidObject = serverResponse.body[0], - url = onBidWon(DEFAULT_ENV, pubcId, bidObject); - - expect(url).to.have.property('protocol'); - expect(url).to.have.property('hostname'); - expect(url).to.have.property('pathname', EVENTS_PATH); - expect(url).to.have.nested.property('search.eventName', ON_BID_WON); - expect(url).to.have.nested.property('search.pubcId', pubcId); - expect(url).to.have.nested.property('search.bidId', bidObject.requestId); - expect(url).to.have.nested.property('search.cache_buster'); - }); - - describe('onTimeout', function () { - const - data = { - ...cannedValidBidRequests[0], - timeout: 123, - }, - url = onTimeout(DEFAULT_ENV, data); - - expect(url).to.have.property('protocol'); - expect(url).to.have.property('hostname'); - expect(url).to.have.property('pathname', EVENTS_PATH); - expect(url).to.have.nested.property('search.eventName', ON_BIDDER_TIMEOUT); - expect(url).to.have.nested.property('search.bidId', data.bidId); - expect(url).to.have.nested.property('search.pubcId', data.crumbs.pubcid); - expect(url).to.have.nested.property('search.adSpaceId', data.params.adSpaceId); - expect(url).to.have.nested.property('search.timeout', data.timeout); - expect(url).to.have.nested.property('search.cache_buster'); - }); - - describe('getUserSyncs', function () { - expect(getUserSyncs( - DEFAULT_ENV, - { - iframeEnabled: false, - pixelEnabled: false - })).to.deep.equal([]); - expect(getUserSyncs( - PRODUCTION, - { - iframeEnabled: false, - pixelEnabled: true - })).to.deep.equal([{ - type: 'image', - url: userSyncImageUrl(PRODUCTION) - }]); - expect(getUserSyncs( - STAGING, - { - iframeEnabled: true, - pixelEnabled: false - })).to.deep.equal([{ - type: 'iframe', - url: userSyncIframeUrl(STAGING) - }]); - expect(getUserSyncs( - DEVELOPMENT, - { - iframeEnabled: true, - pixelEnabled: true - })).to.deep.equal([{ - type: 'image', - url: userSyncImageUrl(DEVELOPMENT) - }, { - type: 'iframe', - url: userSyncIframeUrl(DEVELOPMENT) - }]); - }); - - describe('domain', function () { - expect(domain(null)).to.deep.equal(DOMAIN); - expect(domain('anything')).to.deep.equal(DOMAIN); - expect(domain(PRODUCTION)).to.deep.equal(DOMAIN); - expect(domain(STAGING)).to.deep.equal(DOMAIN_STAGING); - expect(domain(DEVELOPMENT)).to.deep.equal(DOMAIN_DEVELOPMENT); - }); - - describe('eventsUrl', function () { - expect(eventsUrl(null)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: EVENTS_PATH - })); - expect(eventsUrl('anything')).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: EVENTS_PATH - })); - expect(eventsUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(PRODUCTION), - pathname: EVENTS_PATH - })); - expect(eventsUrl(STAGING)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(STAGING), - pathname: EVENTS_PATH - })); - expect(eventsUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ - hostname: domain(DEVELOPMENT), - pathname: EVENTS_PATH - })); - }); - - describe('bidderUrl', function () { - expect(bidderUrl(null)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: BIDDER_PATH - })); - expect(bidderUrl('anything')).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: BIDDER_PATH - })); - expect(bidderUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(PRODUCTION), - pathname: BIDDER_PATH - })); - expect(bidderUrl(STAGING)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(STAGING), - pathname: BIDDER_PATH - })); - expect(bidderUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ - hostname: domain(DEVELOPMENT), - pathname: BIDDER_PATH - })); - }); - - describe('userSyncIframeUrl', function () { - expect(userSyncIframeUrl(null)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: USER_SYNC_IFRAME_PATH - })); - expect(userSyncIframeUrl('anything')).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: USER_SYNC_IFRAME_PATH - })); - expect(userSyncIframeUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(PRODUCTION), - pathname: USER_SYNC_IFRAME_PATH - })); - expect(userSyncIframeUrl(STAGING)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(STAGING), - pathname: USER_SYNC_IFRAME_PATH - })); - expect(userSyncIframeUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ - hostname: domain(DEVELOPMENT), - pathname: USER_SYNC_IFRAME_PATH - })); - }); - - describe('userSyncImageUrl', function () { - expect(userSyncImageUrl(null)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: USER_SYNC_IMAGE_PATH - })); - expect(userSyncImageUrl('anything')).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(DEFAULT_ENV), - pathname: USER_SYNC_IMAGE_PATH - })); - expect(userSyncImageUrl(PRODUCTION)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(PRODUCTION), - pathname: USER_SYNC_IMAGE_PATH - })); - expect(userSyncImageUrl(STAGING)).to.deep.equal(utils.buildUrl({ - protocol: 'https', - hostname: domain(STAGING), - pathname: USER_SYNC_IMAGE_PATH - })); - expect(userSyncImageUrl(DEVELOPMENT)).to.deep.equal(utils.buildUrl({ - hostname: domain(DEVELOPMENT), - pathname: USER_SYNC_IMAGE_PATH - })); - }); - - describe('conformBidRequest', function () { - expect(conformBidRequest(cannedValidBidRequests[0])).to.deep.equal({ - params: cannedValidBidRequests[0].params, - crumbs: cannedValidBidRequests[0].crumbs, - sizes: cannedValidBidRequests[0].sizes, - bidId: cannedValidBidRequests[0].bidId, - bidderRequestId: cannedValidBidRequests[0].bidderRequestId, - }); - }); - - describe('gdprConsent', function () { - describe('gdpr applies, consent given', function () { - const bidderRequest = { - ...cannedBidderRequest, - gdprConsent: { - gdprApplies: true, - vendorData: {vendorConsents: {[VENDOR_ID]: true}}, - } - }; - expect(gdprConsent(bidderRequest)).to.deep.equal(true); - }); - describe('gdpr applies, consent withdrawn', function () { - const bidderRequest = { - ...cannedBidderRequest, - gdprConsent: { - gdprApplies: true, - vendorData: {vendorConsents: {[VENDOR_ID]: false}}, - } - }; - expect(gdprConsent(bidderRequest)).to.deep.equal(false); - }); - describe('gdpr applies, consent unknown', function () { - const bidderRequest = { - ...cannedBidderRequest, - gdprConsent: { - gdprApplies: true, - vendorData: {}, - } - }; - expect(gdprConsent(bidderRequest)).to.deep.equal(undefined); - }); - }); - - describe('requestsPayload', function () { - const - currency = 'EUR', - debug = true; - - const payload = requestsPayload(debug, currency, cannedValidBidRequests, cannedBidderRequest); - - expect(payload).to.exist.and.have.all.keys( - 'akPbjsVersion', - 'bidRequests', - 'currency', - 'debug', - 'language', - 'refererInfo', - 'deviceInfo', - 'userAgent', - 'gdprApplies', - 'gdprConsent', - ); - - expect(payload.bidRequests[0]).to.exist.and.have.all.keys( - 'params', - 'crumbs', - 'sizes', - 'bidId', - 'bidderRequestId', - ); - - expect(payload.akPbjsVersion).to.deep.equal(ADAPTER_VERSION); - expect(payload.bidRequests[0].params).to.deep.equal(cannedValidBidRequests[0].params); - expect(payload.bidRequests[0].crumbs).to.deep.equal(cannedValidBidRequests[0].crumbs); - expect(payload.bidRequests[0].mediaTypes).to.deep.equal(cannedValidBidRequests[0].mediaTypes); - expect(payload.bidRequests[0].bidId).to.deep.equal(cannedValidBidRequests[0].bidId); - expect(payload.bidRequests[0].bidderRequestId).to.deep.equal(cannedValidBidRequests[0].bidderRequestId); - expect(payload.currency).to.deep.equal(currency); - expect(payload.debug).to.deep.equal(debug); - expect(payload.language).to.deep.equal(navigator.language); - expect(payload.deviceInfo).to.exist.and.have.all.keys( - 'browserWidth', - 'browserHeight', - 'deviceWidth', - 'deviceHeight', - 'documentWidth', - 'documentHeight', - 'webGL', - ); - expect(payload.userAgent).to.deep.equal(navigator.userAgent); - expect(payload.gdprApplies).to.deep.equal(cannedBidderRequest.gdprConsent.gdprApplies); - }); - - describe('getViewDimensions', function () { - const window = { - innerWidth: 1024, - innerHeight: 768 - }; - const documentWithElement = { - documentElement: - { - clientWidth: 512, - clientHeight: 384 - } - }; - const documentWithBody = { - body: - { - clientWidth: 512, - clientHeight: 384 - } - }; - expect(getViewDimensions(window, documentWithElement)).to.deep.equal({ - width: 1024, - height: 768 - }); - expect(getViewDimensions(window, documentWithBody)).to.deep.equal({width: 1024, height: 768}); - expect(getViewDimensions(window, documentWithElement)).to.deep.equal({ - width: 1024, - height: 768 - }); - expect(getViewDimensions(window, documentWithBody)).to.deep.equal({width: 1024, height: 768}); - expect(getViewDimensions({}, documentWithElement)).to.deep.equal({width: 512, height: 384}); - expect(getViewDimensions({}, documentWithBody)).to.deep.equal({width: 512, height: 384}); - }); - - describe('getDeviceDimensions', function () { - const window = {screen: {width: 1024, height: 768}}; - expect(getDeviceDimensions(window)).to.deep.equal({width: 1024, height: 768}); - expect(getDeviceDimensions({})).to.deep.equal({width: '', height: ''}); - }); - - describe('getDocumentDimensions', function () { - expect(getDocumentDimensions({ - documentElement: { - clientWidth: 1, - clientHeight: 1, - offsetWidth: 0, - offsetHeight: 0, - scrollWidth: 0, - scrollHeight: 0, - }, - })).to.deep.equal({width: 1, height: 1}); - - expect(getDocumentDimensions({ - documentElement: { - clientWidth: 1, - clientHeight: 1, - offsetWidth: 0, - offsetHeight: 0, - scrollWidth: 0, - scrollHeight: 0, - }, - body: { - scrollHeight: 0, - offsetHeight: 0, - } - })).to.deep.equal({width: 1, height: 1}); - - expect(getDocumentDimensions({ - documentElement: { - clientWidth: 0, - clientHeight: 0, - offsetWidth: 1, - offsetHeight: 1, - scrollWidth: 0, - scrollHeight: 0, - }, - body: { - scrollHeight: 0, - offsetHeight: 0, - } - })).to.deep.equal({width: 1, height: 1}); - - expect(getDocumentDimensions({ - documentElement: { - clientWidth: 0, - clientHeight: 0, - offsetWidth: 0, - offsetHeight: 0, - scrollWidth: 1, - scrollHeight: 1, - }, - body: { - scrollHeight: 0, - offsetHeight: 0, - } - })).to.deep.equal({width: 1, height: 1}); - - expect(getDocumentDimensions({ - documentElement: { - clientWidth: undefined, - clientHeight: undefined, - offsetWidth: undefined, - offsetHeight: undefined, - scrollWidth: undefined, - scrollHeight: undefined, - }, - body: { - scrollHeight: undefined, - offsetHeight: undefined, - } - })).to.deep.equal({width: '', height: ''}); - }); - - // describe('isWebGLEnabled', function () { - // it('handles no webgl', function () { - // const - // document = new Document(), - // canvas = sinon.createStubInstance(HTMLCanvasElement); - // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); - // canvas.getContext.withArgs('webgl').returns(undefined); - // canvas.getContext.withArgs('experimental-webgl').returns(undefined); - // expect(isWebGLEnabled(document)).to.equal(false); - // }); - // - // it('handles webgl exception', function () { - // const - // document = new Document(), - // canvas = sinon.createStubInstance(HTMLCanvasElement); - // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); - // canvas.getContext.withArgs('webgl').throws(DOMException); - // expect(isWebGLEnabled(document)).to.equal(false); - // }); - // - // it('handles experimental webgl', function () { - // const - // document = new Document(), - // canvas = sinon.createStubInstance(HTMLCanvasElement); - // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); - // canvas.getContext.withArgs('webgl').returns(undefined); - // canvas.getContext.withArgs('experimental-webgl').returns(true); - // expect(isWebGLEnabled(document)).to.equal(true); - // }); - // - // it('handles experimental webgl exception', function () { - // const - // document = new Document(), - // canvas = sinon.createStubInstance(HTMLCanvasElement); - // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); - // canvas.getContext.withArgs('webgl').returns(undefined); - // canvas.getContext.withArgs('experimental-webgl').throws(DOMException); - // expect(isWebGLEnabled(document)).to.equal(false); - // }); - // - // it('handles webgl', function () { - // const - // document = new Document(), - // canvas = sinon.createStubInstance(HTMLCanvasElement); - // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); - // canvas.getContext.withArgs('webgl').returns(true); - // expect(isWebGLEnabled(document)).to.equal(true); - // }); - // }); - - describe('getDeviceInfo', function () { - expect(getDeviceInfo( - {width: 1, height: 2}, - {width: 3, height: 4}, - {width: 5, height: 6}, - true - )).to.deep.equal({ - deviceWidth: 1, - deviceHeight: 2, - browserWidth: 3, - browserHeight: 4, - documentWidth: 5, - documentHeight: 6, - webGL: true - }); - }); - - describe('resolveEnv', function () { - it('defaults to production', function () { - expect(resolveEnv({}, null)).to.deep.equal(DEFAULT_ENV); - }); - expect(resolveEnv({}, PRODUCTION)).to.deep.equal(PRODUCTION); - expect(resolveEnv({}, STAGING)).to.deep.equal(STAGING); - expect(resolveEnv({}, DEVELOPMENT)).to.deep.equal(DEVELOPMENT); - expect(resolveEnv({emoteev: {env: PRODUCTION}}, null)).to.deep.equal(PRODUCTION); - expect(resolveEnv({emoteev: {env: STAGING}}, null)).to.deep.equal(STAGING); - expect(resolveEnv({emoteev: {env: DEVELOPMENT}}, null)).to.deep.equal(DEVELOPMENT); - it('prioritizes parameter over configuration', function () { - expect(resolveEnv({emoteev: {env: STAGING}}, DEVELOPMENT)).to.deep.equal(DEVELOPMENT); - }); - }); - - describe('resolveDebug', function () { - it('defaults to production', function () { - expect(resolveDebug({}, null)).to.deep.equal(false); - }); - expect(resolveDebug({}, 'false')).to.deep.equal(false); - expect(resolveDebug({}, 'true')).to.deep.equal(true); - expect(resolveDebug({debug: true}, null)).to.deep.equal(true); - it('prioritizes parameter over configuration', function () { - expect(resolveDebug({debug: true}, 'false')).to.deep.equal(false); - }); - }); - - describe('side effects', function () { - let triggerPixelStub; - let getCookieSpy; - let getConfigSpy; - let getParameterByNameSpy; - - before(function() { - config.resetConfig(); - }); - after(function() { - config.resetConfig(); - }); - beforeEach(function () { - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); - getCookieSpy = sinon.spy(storage, 'getCookie'); - getConfigSpy = sinon.spy(config, 'getConfig'); - getParameterByNameSpy = sinon.spy(utils, 'getParameterByName'); - }); - afterEach(function () { - triggerPixelStub.restore(); - getCookieSpy.restore(); - getConfigSpy.restore(); - getParameterByNameSpy.restore(); - }); - - describe('isBidRequestValid', function () { - it('has intended side-effects', function () { - const validBidRequest = { - bidder: 'emoteev', - bidId: '23a45b4e3', - params: { - adSpaceId: 12345, - }, - mediaTypes: { - banner: { - sizes: [[750, 200]] - } - }, - }; - spec.isBidRequestValid(validBidRequest); - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // sinon.assert.notCalled(config.getConfig); - sinon.assert.notCalled(utils.getParameterByName); - }); - }); - describe('isBidRequestValid empty request', function() { - it('has intended side-effects empty request', function () { - const invalidBidRequest = {}; - spec.isBidRequestValid(invalidBidRequest); - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // disabling these getConfig tests as they have been flaky in browserstack testing - // sinon.assert.notCalled(config.getConfig); - sinon.assert.notCalled(utils.getParameterByName); - }); - }); - describe('buildRequests', function () { - it('has intended side-effects', function () { - spec.buildRequests(cannedValidBidRequests, cannedBidderRequest); - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // sinon.assert.callCount(config.getConfig, 3); - sinon.assert.callCount(utils.getParameterByName, 2); - }); - }); - describe('interpretResponse', function () { - it('has intended side-effects', function () { - spec.interpretResponse(serverResponse); - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // sinon.assert.notCalled(config.getConfig); - sinon.assert.notCalled(utils.getParameterByName); - }); - }); - describe('onBidWon', function () { - it('has intended side-effects', function () { - const bidObject = serverResponse.body[0]; - spec.onBidWon(bidObject); - sinon.assert.calledOnce(utils.triggerPixel); - sinon.assert.calledOnce(storage.getCookie); - // sinon.assert.calledOnce(config.getConfig); - sinon.assert.calledOnce(utils.getParameterByName); - }); - }); - describe('onTimeout', function () { - it('has intended side-effects', function () { - spec.onTimeout(cannedValidBidRequests[0]); - sinon.assert.calledOnce(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // sinon.assert.calledOnce(config.getConfig); - sinon.assert.calledOnce(utils.getParameterByName); - }); - }); - describe('getUserSyncs', function () { - it('has intended side-effects', function () { - spec.getUserSyncs({}); - sinon.assert.notCalled(utils.triggerPixel); - sinon.assert.notCalled(storage.getCookie); - // sinon.assert.calledOnce(config.getConfig); - sinon.assert.calledOnce(utils.getParameterByName); - }); - }); - }); - - describe('validateSizes', function () { - it('only accepts valid array of sizes', function () { - expect(validateSizes([])).to.deep.equal(false); - expect(validateSizes([[]])).to.deep.equal(false); - expect(validateSizes([[450, 450], undefined])).to.deep.equal(false); - expect(validateSizes([[450, 450], 'size'])).to.deep.equal(false); - expect(validateSizes([[1, 1]])).to.deep.equal(true); - expect(validateSizes([[1, 1], [450, 450]])).to.deep.equal(true); - }); - }); - - describe('validateContext', function () { - it('only accepts valid context', function () { - expect(validateContext(IN_CONTENT)).to.deep.equal(true); - expect(validateContext(FOOTER)).to.deep.equal(true); - expect(validateContext(OVERLAY)).to.deep.equal(true); - expect(validateContext(WALLPAPER)).to.deep.equal(true); - expect(validateContext(null)).to.deep.equal(false); - expect(validateContext('anything else')).to.deep.equal(false); - }); - }); - - describe('validateExternalId', function () { - it('only accepts a positive integer or null', function () { - expect(validateExternalId(0)).to.deep.equal(false); - expect(validateExternalId(42)).to.deep.equal(true); - expect(validateExternalId(42.0)).to.deep.equal(true); // edge case: valid externalId - expect(validateExternalId(3.14159)).to.deep.equal(false); - expect(validateExternalId('externalId')).to.deep.equal(false); - expect(validateExternalId(undefined)).to.deep.equal(true); - expect(validateExternalId(null)).to.deep.equal(true); - }); - }); -}); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 7be8a2ce5ac..5831a8506c1 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -253,12 +253,38 @@ describe('emx_digital Adapter', function () { expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); }); - it('builds with bid floor', function () { - const bidRequestWithBidFloor = utils.deepClone(bidderRequest.bids); - bidRequestWithBidFloor[0].params.bidfloor = 1; - const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + it('builds bidfloor value from bid param when getFloor function does not exist', function () { + const bidRequestWithFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithFloor, bidderRequest); const data = JSON.parse(requestWithFloor.data); - expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithFloor[0].params.bidfloor); + }); + + it('builds bidfloor value from getFloor function when it exists', function () { + const floorResponse = { currency: 'USD', floor: 3 }; + const bidRequestWithGetFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithGetFloor[0].getFloor = () => floorResponse; + const requestWithGetFloor = spec.buildRequests(bidRequestWithGetFloor, bidderRequest); + const data = JSON.parse(requestWithGetFloor.data); + expect(data.imp[0].bidfloor).to.equal(3); + }); + + it('builds bidfloor value from getFloor when both floor and getFloor function exists', function () { + const floorResponse = { currency: 'USD', floor: 3 }; + const bidRequestWithBothFloors = utils.deepClone(bidderRequest.bids); + bidRequestWithBothFloors[0].params.bidfloor = 1; + bidRequestWithBothFloors[0].getFloor = () => floorResponse; + const requestWithBothFloors = spec.buildRequests(bidRequestWithBothFloors, bidderRequest); + const data = JSON.parse(requestWithBothFloors.data); + expect(data.imp[0].bidfloor).to.equal(3); + }); + + it('empty bidfloor value when floor and getFloor is not defined', function () { + const bidRequestWithoutFloor = utils.deepClone(bidderRequest.bids); + const requestWithoutFloor = spec.buildRequests(bidRequestWithoutFloor, bidderRequest); + const data = JSON.parse(requestWithoutFloor.data); + expect(data.imp[0].bidfloor).to.not.exist; }); it('builds request properly', function () { @@ -367,6 +393,61 @@ describe('emx_digital Adapter', function () { expect(request.us_privacy).to.exist; expect(request.us_privacy).to.exist.and.to.equal(consentString); }); + + it('should add schain object to request', function() { + const schainBidderRequest = utils.deepClone(bidderRequest); + schainBidderRequest.bids[0].schain = { + 'complete': 1, + 'ver': '1.0', + 'nodes': [ + { + 'asi': 'testing.com', + 'sid': 'abc', + 'hp': 1 + } + ] + }; + let request = spec.buildRequests(schainBidderRequest.bids, schainBidderRequest); + request = JSON.parse(request.data); + expect(request.source.ext.schain).to.exist; + expect(request.source.ext.schain).to.have.property('complete', 1); + expect(request.source.ext.schain).to.have.property('ver', '1.0'); + expect(request.source.ext.schain.nodes[0].asi).to.equal(schainBidderRequest.bids[0].schain.nodes[0].asi); + }); + + it('should add liveramp identitylink id to request', () => { + const idl_env = '123'; + const bidRequestWithID = utils.deepClone(bidderRequest); + bidRequestWithID.userId = { idl_env }; + let requestWithID = spec.buildRequests(bidRequestWithID.bids, bidRequestWithID); + requestWithID = JSON.parse(requestWithID.data); + expect(requestWithID.user.ext.eids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{ + id: idl_env, + ext: { + rtiPartner: 'idl' + } + }] + }); + }); + + it('should add UID 2.0 to request', () => { + const uid2 = { id: '456' }; + const bidRequestWithUID = utils.deepClone(bidderRequest); + bidRequestWithUID.userId = { uid2 }; + let requestWithUID = spec.buildRequests(bidRequestWithUID.bids, bidRequestWithUID); + requestWithUID = JSON.parse(requestWithUID.data); + expect(requestWithUID.user.ext.eids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: uid2.id, + ext: { + rtiPartner: 'UID2' + } + }] + }); + }); }); describe('interpretResponse', function () { @@ -449,7 +530,8 @@ describe('emx_digital Adapter', function () { 'id': '987654321cba', 'price': 0.5, 'ttl': 300, - 'w': 300 + 'w': 300, + 'adomain': ['example.com'] }], 'seat': '1356' }, { @@ -477,7 +559,10 @@ describe('emx_digital Adapter', function () { 'netRevneue': true, 'mediaType': 'banner', 'ad': '', - 'ttl': 300 + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } }, { 'requestId': '12819a18-56e1-4256-b836-b69a10202668', 'cpm': 0.7, @@ -609,15 +694,34 @@ describe('emx_digital Adapter', function () { body: badAdmServerResponse })); }); + + it('returns valid advertiser domain', function () { + const bidResponse = utils.deepClone(serverResponse); + let result = spec.interpretResponse({body: bidResponse}); + expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); + // case where adomains are not in request + expect(result[1].meta).to.not.exist; + }); }); describe('getUserSyncs', function () { - let syncOptionsIframe = { iframeEnabled: true }; - let syncOptionsPixel = { pixelEnabled: true }; - it('Should push the correct sync type depending on the config', function () { - let iframeSync = spec.getUserSyncs(syncOptionsIframe); - expect(iframeSync.length).to.equal(1); - expect(iframeSync[0].type).to.equal('iframe'); + it('should register the iframe sync url', 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'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); }); }); }); diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js new file mode 100644 index 00000000000..3184349cdf7 --- /dev/null +++ b/test/spec/modules/enrichmentFpdModule_spec.js @@ -0,0 +1,105 @@ +import { expect } from 'chai'; +import { getRefererInfo } from 'src/refererDetection.js'; +import { initSubmodule, coreStorage } from 'modules/enrichmentFpdModule.js'; + +describe('the first party data enrichment module', function() { + let width; + let widthStub; + let height; + let heightStub; + let querySelectorStub; + let coreStorageStub; + let canonical; + let keywords; + + before(function() { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + beforeEach(function() { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); + querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); + widthStub = sinon.stub(window.top, 'innerWidth').get(function() { + return width; + }); + heightStub = sinon.stub(window.top, 'innerHeight').get(function() { + return height; + }); + coreStorageStub = sinon.stub(coreStorage, 'getCookie'); + coreStorageStub + .onFirstCall() + .returns(null) // co.uk + .onSecondCall() + .returns('writeable'); // domain.co.uk + }); + + afterEach(function() { + widthStub.restore(); + heightStub.restore(); + querySelectorStub.restore(); + coreStorageStub.restore(); + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + it('adds ref and device values', function() { + width = 800; + height = 500; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.be.undefined; + }); + + it('adds page domain values if canonical url exists', function() { + width = 800; + height = 500; + canonical.href = 'https://www.subdomain.domain.co.uk/path?query=12345'; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.subdomain.domain.co.uk/path?query=12345'); + expect(validated.site.domain).to.equal('subdomain.domain.co.uk'); + expect(validated.site.publisher.domain).to.equal('domain.co.uk'); + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.be.undefined; + }); + + it('adds keyword value if keyword meta content exists', function() { + width = 800; + height = 500; + keywords.content = 'value1,value2,value3'; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.equal('value1,value2,value3'); + }); + + it('does not overwrite existing data from getConfig ortb2', function() { + width = 800; + height = 500; + + let validated = initSubmodule({}, {device: {w: 1200, h: 700}, site: {ref: 'https://someUrl.com', page: 'test.com'}}); + + expect(validated.site.ref).to.equal('https://someUrl.com'); + expect(validated.site.page).to.equal('test.com'); + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 1200, h: 700 }); + expect(validated.site.keywords).to.be.undefined; + }); +}); diff --git a/test/spec/modules/envivoBidAdapter_spec.js b/test/spec/modules/envivoBidAdapter_spec.js deleted file mode 100644 index 7bd1dd1ccf1..00000000000 --- a/test/spec/modules/envivoBidAdapter_spec.js +++ /dev/null @@ -1,159 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/envivoBidAdapter.js'; - -const ENDPOINT = 'https://ad.nvivo.tv/ads/www/admin/plugins/Prebid/getAd.php'; - -describe('The Envivo bidding adapter', function () { - describe('isBidRequestValid', function () { - it('should return false when given an invalid bid', function () { - const bid = { - bidder: 'envivo', - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }); - - it('should return true when given a publisherId in bid', function () { - const bid = { - bidder: 'envivo', - params: { - publisherId: 14 - }, - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [{ - 'bidder': 'envivo', - 'params': { - 'publisherId': 14 - }, - '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(14); - }) - }); - 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': 'envivo', - 'params': { - 'publisherId': 14 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [300, 600] - ] - } - }, - 'bidId': '2ee937f15015c6', - 'src': 'client', - }, - { - 'bidder': 'envivo', - 'params': { - 'publisherId': 14 - }, - '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/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index f267680b46b..6aa191f29a5 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -1,6 +1,9 @@ import { expect } from 'chai'; import { spec, storage } from 'modules/eplanningBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { init } from 'modules/userId/index.js'; +import * as utils from 'src/utils.js'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); @@ -23,6 +26,7 @@ describe('E-Planning Adapter', function () { const I_ID = '7854abc56248f873'; const CRID = '1234567890'; const TEST_ISV = 'leles.e-planning.net'; + const ADOMAIN = 'adomain.com'; const validBid = { 'bidder': 'eplanning', 'bidId': BID_ID, @@ -32,6 +36,15 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE, 'sizes': [[300, 250], [300, 600]], }; + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + }; const ML = '1'; const validBidMappingLinear = { 'bidder': 'eplanning', @@ -43,13 +56,14 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE, 'sizes': [[300, 250], [300, 600]], }; - const validBid2 = { + const SN = 'spaceName'; + const validBidSpaceName = { 'bidder': 'eplanning', - 'bidId': BID_ID2, + 'bidId': BID_ID, 'params': { 'ci': CI, + 'sn': SN, }, - 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; const validBidView = { @@ -95,6 +109,39 @@ describe('E-Planning Adapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], }; + const validBidExistingSizesInPriorityListForMobile = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [320, 50], [300, 50]], + }; + const validBidSizesNotExistingInPriorityListForMobile = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [300, 70], [160, 600]], + }; + const validBidExistingSizesInPriorityListForDesktop = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [300, 600], [300, 250]], + 'ext': { + 'screen': { + 'w': 1025, + 'h': 1025 + } + } + }; const response = { body: { 'sI': { @@ -191,6 +238,39 @@ describe('E-Planning Adapter', function () { ] } }; + const responseWithAdomain = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE, + 'a': [{ + 'adm': ADM, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM, + 'adom': ADOMAIN + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseWithNoSpace = { body: { 'sI': { @@ -247,9 +327,30 @@ describe('E-Planning Adapter', function () { describe('buildRequests', function () { let bidRequests = [validBid]; + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const createWindow = () => { + const win = {}; + win.self = win; + win.innerWidth = 1025; + return win; + }; + + function setupSingleWindow(sandBox) { + const win = createWindow(); + sandBox.stub(utils, 'getWindowSelf').returns(win); + } + it('should create the url correctly', function () { const url = spec.buildRequests(bidRequests, bidderRequest).url; - expect(url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + expect(url).to.equal('https://ads.us.e-planning.net/pbjs/1/' + CI + '/1/localhost/ROS'); }); it('should return GET method', function () { @@ -257,11 +358,6 @@ describe('E-Planning Adapter', function () { expect(method).to.equal('GET'); }); - it('should return r parameter with value pbjs', function () { - const r = spec.buildRequests(bidRequests, bidderRequest).data.r; - expect(r).to.equal('pbjs'); - }); - it('should return pbv parameter with value prebid version', function () { const pbv = spec.buildRequests(bidRequests, bidderRequest).data.pbv; expect(pbv).to.equal('$prebid.version$'); @@ -278,6 +374,12 @@ describe('E-Planning Adapter', function () { expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600'); }); + it('should return e parameter with space name attribute with value according to the adunit sizes', function () { + let bidRequestsSN = [validBidSpaceName]; + const e = spec.buildRequests(bidRequestsSN, bidderRequest).data.e; + expect(e).to.equal(SN + ':300x250,300x600'); + }); + it('should return correct e parameter with more than one adunit', function () { const NEW_CODE = ADUNIT_CODE + '2'; const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; @@ -314,6 +416,23 @@ describe('E-Planning Adapter', function () { expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); }); + it('should return correct e parameter with space name attribute with more than one adunit', function () { + let bidRequestsSN = [validBidSpaceName]; + const NEW_SN = 'anotherNameSpace'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + 'sn': NEW_SN, + }, + 'sizes': [[100, 100]], + }; + bidRequestsSN.push(anotherBid); + + const e = spec.buildRequests(bidRequestsSN, bidderRequest).data.e; + expect(e).to.equal(SN + ':300x250,300x600+' + NEW_SN + ':100x100'); + }); + it('should return correct e parameter when the adunit has no size', function () { const noSizeBid = { 'bidder': 'eplanning', @@ -388,6 +507,25 @@ describe('E-Planning Adapter', function () { const dataRequest = request.data; expect(dataRequest.ccpa).to.equal('consentCcpa'); }); + + it('should return the e parameter with a value according to the sizes in order corresponding to the mobile priority list of the ad units', function () { + let bidRequestsPrioritySizes = [validBidExistingSizesInPriorityListForMobile]; + const e = spec.buildRequests(bidRequestsPrioritySizes, bidderRequest).data.e; + expect(e).to.equal('320x50_0:320x50,300x50,970x250'); + }); + + it('should return the e parameter with a value according to the sizes in order corresponding to the desktop priority list of the ad units', function () { + let bidRequestsPrioritySizes = [validBidExistingSizesInPriorityListForDesktop]; + setupSingleWindow(sandbox); + const e = spec.buildRequests(bidRequestsPrioritySizes, bidderRequest).data.e; + expect(e).to.equal('300x250_0:300x250,300x600,970x250'); + }); + + it('should return the e parameter with a value according to the sizes in order as they are sent from the ad units', function () { + let bidRequestsPrioritySizes2 = [validBidSizesNotExistingInPriorityListForMobile]; + const e = spec.buildRequests(bidRequestsPrioritySizes2, bidderRequest).data.e; + expect(e).to.equal('970x250_0:970x250,300x70,160x600'); + }); }); describe('interpretResponse', function () { @@ -416,6 +554,25 @@ describe('E-Planning Adapter', function () { }; expect(bidResponse).to.deep.equal(expectedResponse); }); + + it('should pass advertiserDomains when present', function () { + const bidResponse = spec.interpretResponse(responseWithAdomain, { adUnitToBidId: { [CLEAN_ADUNIT_CODE]: BID_ID } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ad: ADM, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ADOMAIN + } + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); }); describe('getUserSyncs', function () { @@ -583,7 +740,7 @@ describe('E-Planning Adapter', function () { hasLocalStorageStub.returns(false); const response = spec.buildRequests(bidRequests, bidderRequest); - expect(response.url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + expect(response.url).to.equal('https://ads.us.e-planning.net/pbjs/1/' + CI + '/1/localhost/ROS'); expect(response.data.vs).to.equal('F'); sinon.assert.notCalled(getLocalStorageSpy); @@ -593,7 +750,7 @@ describe('E-Planning Adapter', function () { it('should create the url correctly with LocalStorage', function() { createElementVisible(); const response = spec.buildRequests(bidRequests, bidderRequest); - expect(response.url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + expect(response.url).to.equal('https://ads.us.e-planning.net/pbjs/1/' + CI + '/1/localhost/ROS'); expect(response.data.vs).to.equal('F'); @@ -796,4 +953,26 @@ describe('E-Planning Adapter', function () { }); }); }); + describe('Send eids', function() { + it('should add eids to the request', function() { + init(config); + config.setConfig({ + userSync: { + userIds: [ + { name: 'id5Id', value: { 'id5id': { uid: 'ID5-ZHMOL_IfFSt7_lVYX8rBZc6GH3XMWyPQOBUfr4bm0g!', ext: { linkType: 1 } } } }, + { name: 'pubCommonId', value: {'pubcid': 'c29cb2ae-769d-42f6-891a-f53cadee823d'} }, + { name: 'unifiedId', value: {'tdid': 'D6885E90-2A7A-4E0F-87CB-7734ED1B99A3'} } + ] + } + }); + let bidRequests = [validBidView]; + const expected_id5id = encodeURIComponent(JSON.stringify({ uid: 'ID5-ZHMOL_IfFSt7_lVYX8rBZc6GH3XMWyPQOBUfr4bm0g!', ext: { linkType: 1 } })); + const request = spec.buildRequests(bidRequests, bidderRequest); + const dataRequest = request.data; + + expect('D6885E90-2A7A-4E0F-87CB-7734ED1B99A3').to.equal(dataRequest.e_tdid); + expect('c29cb2ae-769d-42f6-891a-f53cadee823d').to.equal(dataRequest.e_pubcid); + expect(expected_id5id).to.equal(dataRequest.e_id5id); + }); + }); }); diff --git a/test/spec/modules/etargetBidAdapter_spec.js b/test/spec/modules/etargetBidAdapter_spec.js index 4f5e0c224ec..55394dcdeea 100644 --- a/test/spec/modules/etargetBidAdapter_spec.js +++ b/test/spec/modules/etargetBidAdapter_spec.js @@ -1,6 +1,7 @@ import {assert, expect} from 'chai'; import {spec} from 'modules/etargetBidAdapter.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { deepClone } from 'src/utils.js'; describe('etarget adapter', function () { let serverResponse, bidRequest, bidResponses; @@ -26,6 +27,11 @@ describe('etarget adapter', function () { assert.lengthOf(parsedUrl.items, 7); }); + it('should be an object', function () { + let request = spec.buildRequests(bids); + assert.isNotNull(request.metaData); + }); + it('should handle global request parameters', function () { let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url); assert.equal(parsedUrl.path, 'https://sk.search.etargetnet.com/hb'); @@ -36,6 +42,16 @@ describe('etarget adapter', function () { assert.equal(request.method, 'POST'); }); + it('should attach floor param when either bid param or getFloor function exists', function () { + // let getFloorResponse = { currency: 'EUR', floor: 5 }; + let request = null; + let bidRequest = deepClone(bids[0]); + + // floor param has to be NULL + request = spec.buildRequests([bidRequest]); + assert.equal(typeof request.floors, 'undefined'); + }); + it('should correctly form bid items', function () { let bidList = bids; let request = spec.buildRequests(bidList); @@ -154,6 +170,7 @@ describe('etarget adapter', function () { assert.equal(result.height, 250); assert.equal(result.currency, 'EUR'); assert.equal(result.netRevenue, true); + assert.isNotNull(result.reason); assert.equal(result.ttl, 360); assert.equal(result.ad, ''); assert.equal(result.transactionId, '5f33781f-9552-4ca1'); diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js new file mode 100644 index 00000000000..c250c8e5e8b --- /dev/null +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -0,0 +1,134 @@ +import * as utils from '../../../src/utils.js'; +import {server} from '../../mocks/xhr.js'; + +import {fabrickIdSubmodule, appendUrl} from 'modules/fabrickIdSystem.js'; + +const defaultConfigParams = { + apiKey: '123', + e: 'abc', + p: ['def', 'hij'], + url: 'http://localhost:9999/test/mocks/fabrickId.json?' +}; +const responseHeader = {'Content-Type': 'application/json'} + +describe('Fabrick ID System', function() { + let logErrorStub; + + beforeEach(function () { + }); + + afterEach(function () { + }); + + it('should log an error if no configParams were passed into getId', function () { + logErrorStub = sinon.stub(utils, 'logError'); + fabrickIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); + }); + + it('should error on json parsing', function() { + logErrorStub = sinon.stub(utils, 'logError'); + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: defaultConfigParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + request.respond( + 200, + responseHeader, + '] this is not json {' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); + }); + + it('should truncate the params', function() { + let r = ''; + for (let i = 0; i < 1500; i++) { + r += 'r'; + } + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: r, + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + r = ''; + for (let i = 0; i < 1000 - 3; i++) { + r += 'r'; + } + expect(request.url).to.match(new RegExp(`r=${r}&r=`)); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should complete successfully', function() { + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: 'r-0', + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.match(/r=r-0&r=s-0&r=cu-0&r=http/); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should truncate 2', function() { + let configParams = { + maxUrlLen: 10, + maxRefLen: 5, + maxSpaceAvailable: 2 + }; + + let url = appendUrl('', 'r', '123', configParams); + expect(url).to.equal('&r=12'); + + url = appendUrl('12345', 'r', '678', configParams); + expect(url).to.equal('12345&r=67'); + + url = appendUrl('12345678', 'r', '9', configParams); + expect(url).to.equal('12345678'); + + configParams.maxRefLen = 8; + url = appendUrl('', 'r', '1234&', configParams); + expect(url).to.equal('&r=1234'); + + url = appendUrl('', 'r', '123&', configParams); + expect(url).to.equal('&r=123'); + + url = appendUrl('', 'r', '12&', configParams); + expect(url).to.equal('&r=12%26'); + + url = appendUrl('', 'r', '1&&', configParams); + expect(url).to.equal('&r=1%26'); + }); +}); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 066ab6b21f6..6b75af0d55d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -13,7 +13,7 @@ describe('FeedAdAdapter', function () { it('should only support video and banner ads', function () { expect(spec.supportedMediaTypes).to.be.a('array'); expect(spec.supportedMediaTypes).to.include(BANNER); - expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); it('should export the BidderSpec functions', function () { @@ -23,6 +23,9 @@ describe('FeedAdAdapter', function () { expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); }); + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(781); + }) }); describe('isBidRequestValid', function () { @@ -248,6 +251,40 @@ describe('FeedAdAdapter', function () { let result = spec.buildRequests([bid, bid, bid]); expect(result).to.be.empty; }); + it('should not include GDPR data if the bidder request has none available', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.gdprApplies).to.be.undefined; + expect(result.data.consentIabTcf).to.be.undefined; + }); + it('should include GDPR data if the bidder requests contains it', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let request = Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'the consent string', + gdprApplies: true + } + }); + let result = spec.buildRequests([bid], request); + expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); + expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); + }); }); describe('interpretResponse', function () { @@ -404,7 +441,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.0' + sdk_version: '1.0.2' }; subject(data); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/fidelityBidAdapter_spec.js b/test/spec/modules/fidelityBidAdapter_spec.js deleted file mode 100644 index 304a98675b3..00000000000 --- a/test/spec/modules/fidelityBidAdapter_spec.js +++ /dev/null @@ -1,233 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/fidelityBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('FidelityAdapter', 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': 'fidelity', - 'params': { - 'zoneid': '27248', - 'floor': '0.05', - 'server': 'x.fidelity-media.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': '27248', - }; - 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: 'fidelity', - requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - bidderRequestId: '178e34bad3658f', - bids: [ - { - bidder: 'fidelity', - params: { - zoneid: '27248', - floor: '0.05', - server: 'x.fidelity-media.com', - }, - placementCode: '/19968336/header-bid-tag-0', - sizes: [[300, 250], [320, 50]], - bidId: '2ffb201a808da7', - bidderRequestId: '178e34bad3658f', - requestId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - schain: { - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'publisher', - domain: 'publisher.com' - }] - } - } - ], - start: 1472239426002, - auctionStart: 1472239426000, - timeout: 5000, - refererInfo: { - referer: 'http://test.com/index.html' - } - }; - - it('should add params to the request', function () { - let schainString = '1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const payload = request.data; - expect(payload.from).to.exist; - expect(payload.v).to.exist; - expect(payload.requestid).to.exist; - expect(payload.impid).to.exist; - expect(payload.zoneid).to.exist; - expect(payload.floor).to.exist; - expect(payload.charset).to.exist; - expect(payload.subid).to.exist; - expect(payload.flashver).to.exist; - expect(payload.tmax).to.exist; - expect(payload.defloc).to.exist; - expect(payload.schain).to.exist.and.to.be.a('string'); - expect(payload.schain).to.equal(schainString); - }); - - it('should add consent information to the request - TCF v1', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let uspConsentString = '1YN-'; - bidderRequest.gdprConsent = { - gdprApplies: true, - allowAuctionWithoutConsent: true, - consentString: consentString, - vendorData: { - vendorConsents: { - '408': true - }, - }, - apiVersion: 1 - }; - bidderRequest.uspConsent = uspConsentString; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const payload = request.data; - expect(payload.gdpr).to.exist.and.to.be.a('number'); - expect(payload.gdpr).to.equal(1); - expect(payload.consent_str).to.exist.and.to.be.a('string'); - expect(payload.consent_str).to.equal(consentString); - expect(payload.consent_given).to.exist.and.to.be.a('number'); - expect(payload.consent_given).to.equal(1); - expect(payload.us_privacy).to.exist.and.to.be.a('string'); - expect(payload.us_privacy).to.equal(uspConsentString); - }); - - it('should add consent information to the request - TCF v2', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let uspConsentString = '1YN-'; - bidderRequest.gdprConsent = { - gdprApplies: true, - allowAuctionWithoutConsent: true, - consentString: consentString, - vendorData: { - vendor: { - consents: { - '408': true - } - }, - }, - apiVersion: 2 - }; - bidderRequest.uspConsent = uspConsentString; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const payload = request.data; - expect(payload.gdpr).to.exist.and.to.be.a('number'); - expect(payload.gdpr).to.equal(1); - expect(payload.consent_str).to.exist.and.to.be.a('string'); - expect(payload.consent_str).to.equal(consentString); - expect(payload.consent_given).to.exist.and.to.be.a('number'); - expect(payload.consent_given).to.equal(1); - expect(payload.us_privacy).to.exist.and.to.be.a('string'); - expect(payload.us_privacy).to.equal(uspConsentString); - }); - - it('sends bid request to ENDPOINT via GET', function () { - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.url).to.equal('https://x.fidelity-media.com/delivery/hb.php'); - expect(request.method).to.equal('GET'); - }); - }) - - describe('interpretResponse', function () { - let response = { - 'id': '543210', - 'seatbid': [ { - 'bid': [ { - 'id': '1111111', - 'impid': 'bidId-123456-1', - 'price': 0.09, - 'adm': '', - 'width': 728, - 'height': 90, - } ] - } ] - }; - - 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://x.fidelity-media.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/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 29136c85241..54d1a7e7976 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -12,7 +12,7 @@ function setCookie(name, value, expires) { document.cookie = name + '=' + value + '; path=/' + (expires ? ('; expires=' + expires.toUTCString()) : '') + - '; SameSite=None'; + '; SameSite=Lax'; } describe('finteza analytics adapter', function () { @@ -119,11 +119,11 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); - expect(server.requests.length).to.equal(2); - expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); + expect(server.requests.length).to.equal(2); + let url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); @@ -173,11 +173,11 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_WON, bidWon); - expect(server.requests.length).to.equal(1); - expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); + expect(server.requests.length).to.equal(1); + const url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); @@ -212,11 +212,11 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - expect(server.requests.length).to.equal(1); - expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); + expect(server.requests.length).to.equal(1); + const url = parseUrl(server.requests[0].url); expect(url.protocol).to.equal('https'); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js deleted file mode 100644 index 6530a3c36cf..00000000000 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ /dev/null @@ -1,201 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/fluctBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; - -describe('fluctAdapter', 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: 'fluct', - params: { - dfpUnitCode: '/1000/dfp_unit_code', - tagId: '10000:100000001', - groupId: '1000000002', - } - }; - 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 = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true when dfpUnitCode is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - tagId: '10000:100000001', - groupId: '1000000002', - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when groupId is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - dfpUnitCode: '/1000/dfp_unit_code', - tagId: '10000:100000001', - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [{ - bidder: 'fluct', - params: { - dfpUnitCode: '/100000/unit_code', - tagId: '10000:100000001', - groupId: '1000000002', - }, - adUnitCode: '/10000/unit_code', - sizes: [[300, 250], [336, 280]], - bidId: '237f4d1a293f99', - bidderRequestId: '1a857fa34c1c96', - auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', - transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', - }]; - const bidderRequest = { - refererInfo: { - referer: 'http://example.com' - } - }; - - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.method).to.equal('POST'); - }); - }); - - describe('interpretResponse', function() { - const callBeaconSnippet = ''; - - it('should get correct bid response', function() { - const bidRequest = { - bidder: 'fluct', - params: { - dfpUnitCode: '/10000/unit_code', - tagid: '10000:100000001', - groupId: '1000000002', - }, - adUnitCode: '/10000/unit_code', - sizes: [[300, 250], [336, 280]], - bidId: '237f4d1a293f99', - bidderRequestId: '1a857fa34c1c96', - auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', - transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', - }; - - const serverResponse = { - body: { - id: '237f4d1a293f99', - cur: 'JPY', - seatbid: [{ - bid: [{ - price: 100, - w: 300, - h: 250, - adm: '', - burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', - crid: 'test_creative', - }] - }] - } - }; - - const expectedResponse = [ - { - bidderCode: 'fluct', - requestId: '237f4d1a293f99', - currency: 'JPY', - cpm: 100, - netRevenue: true, - width: 300, - height: 250, - creativeId: 'test_creative', - ttl: 300, - ad: '' + callBeaconSnippet, - } - ]; - - const result = spec.interpretResponse(serverResponse, bidRequest); - expect(result).to.have.lengthOf(1); - expect(result).to.deep.have.same.members(expectedResponse); - }); - - it('should get correct bid response with dealId', function() { - const bidRequest = { - bidder: 'fluct', - params: { - dfpUnitCode: '/10000/unit_code', - tagid: '10000:100000001', - groupId: '1000000002' - }, - adUnitCode: '/10000/unit_code', - sizes: [[300, 250], [336, 280]], - bidId: '237f4d1a293f99', - bidderRequestId: '1a857fa34c1c96', - auctionId: 'a297d1aa-7900-4ce4-a0aa-caa8d46c4af7', - transactionId: '00b2896c-2731-4f01-83e4-7a3ad5da13b6', - }; - - const serverResponse = { - body: { - id: '237f4d1a293f99', - cur: 'JPY', - seatbid: [{ - bid: [{ - price: 100, - w: 300, - h: 250, - adm: '', - burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', - crid: 'test_creative', - dealid: 'test_deal', - }] - }] - } - }; - - const expectedResponse = [ - { - bidderCode: 'fluct', - requestId: '237f4d1a293f99', - currency: 'JPY', - cpm: 100, - netRevenue: true, - width: 300, - height: 250, - creativeId: 'test_creative', - ttl: 300, - ad: '' + callBeaconSnippet, - dealId: 'test_deal', - } - ]; - - const result = spec.interpretResponse(serverResponse, bidRequest); - expect(result).to.have.lengthOf(1); - expect(result).to.deep.have.same.members(expectedResponse); - }); - - it('should get empty response when bid server returns 204', function() { - expect(spec.interpretResponse({})).to.be.empty; - }); - }); -}); diff --git a/test/spec/modules/fpdModule_spec.js b/test/spec/modules/fpdModule_spec.js new file mode 100644 index 00000000000..c2a6c41835e --- /dev/null +++ b/test/spec/modules/fpdModule_spec.js @@ -0,0 +1,464 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; +import {getRefererInfo} from 'src/refererDetection.js'; +import {init, registerSubmodules} from 'modules/fpdModule/index.js'; +import * as enrichmentModule from 'modules/enrichmentFpdModule.js'; +import * as validationModule from 'modules/validationFpdModule/index.js'; + +let enrichments = { + name: 'enrichments', + queue: 2, + init: enrichmentModule.initSubmodule +}; +let validations = { + name: 'validations', + queue: 1, + init: validationModule.initSubmodule +}; + +describe('the first party data 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'] + } + } + } + }; + + afterEach(function () { + config.resetConfig(); + }); + + describe('first party data intitializing', function () { + let width; + let widthStub; + let height; + let heightStub; + let querySelectorStub; + let canonical; + let keywords; + + before(function() { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + beforeEach(function() { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); + querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); + widthStub = sinon.stub(window.top, 'innerWidth').get(function () { + return width; + }); + heightStub = sinon.stub(window.top, 'innerHeight').get(function () { + return height; + }); + }); + + afterEach(function() { + widthStub.restore(); + heightStub.restore(); + querySelectorStub.restore(); + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + it('sets default referer and dimension values to ortb2 data', function () { + registerSubmodules(enrichments); + registerSubmodules(validations); + + let validated; + + width = 1120; + height = 750; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('sets page and domain values to ortb2 data if canonical link exists', function () { + let validated; + + canonical.href = 'https://www.domain.com/path?query=12345'; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('sets keyword values to ortb2 data if keywords meta exists', function () { + let validated; + + keywords.content = 'value1,value2,value3'; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.equal('value1,value2,value3'); + }); + + it('only sets values that do not exist in ortb2 config', function () { + let validated; + + config.setConfig({ortb2: {site: {ref: 'https://testpage.com', domain: 'newDomain.com'}}}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://testpage.com'); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.equal('newDomain.com'); + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('filters ortb2 data that is set', function () { + let validated; + let conf = { + ortb2: { + user: { + data: {}, + gender: 'f', + age: 45 + }, + site: { + content: { + data: [{ + segment: { + test: 1 + }, + name: 'foo' + }, { + segment: [{ + id: 'test' + }, { + id: 3 + }], + name: 'bar' + }] + } + }, + device: { + w: 1, + h: 1 + } + } + }; + + config.setConfig(conf); + canonical.href = 'https://www.domain.com/path?query=12345'; + width = 1120; + height = 750; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.site.content.data).to.deep.equal([{segment: [{id: 'test'}], name: 'bar'}]); + expect(validated.user.data).to.be.undefined; + expect(validated.device).to.deep.to.equal({w: 1, h: 1}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('should not overwrite existing data with default settings', function () { + let validated; + let conf = { + ortb2: { + site: { + ref: 'https://referer.com' + } + } + }; + + config.setConfig(conf); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://referer.com'); + }); + + it('should allow overwrite default data with setConfig', function () { + let validated; + let conf = { + ortb2: { + site: { + ref: 'https://referer.com' + } + } + }; + + config.setConfig(conf); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://referer.com'); + }); + + it('should filter all data', function () { + let validated; + let conf = { + imp: [], + site: { + name: 123, + domain: 456, + page: 789, + ref: 987, + keywords: ['keywords'], + search: 654, + cat: 'cat', + sectioncat: 'sectioncat', + pagecat: 'pagecat', + content: { + data: [{ + name: 1, + segment: [] + }] + } + }, + user: { + yob: 'twenty', + gender: 0, + keywords: ['foobar'], + data: ['test'] + }, + device: [800, 450], + cur: { + adServerCurrency: 'USD' + } + }; + + config.setConfig({'firstPartyData': {skipEnrichments: true}}); + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated).to.deep.equal({}); + }); + + it('should add enrichments but not alter any arbitrary ortb2 data', function () { + let validated; + let conf = { + site: { + ext: { + data: { + inventory: ['value1'] + } + } + }, + user: { + ext: { + data: { + visitor: ['value2'] + } + } + }, + cur: ['USD'] + }; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.ext.data).to.deep.equal({inventory: ['value1']}); + expect(validated.user.ext.data).to.deep.equal({visitor: ['value2']}); + expect(validated.cur).to.deep.equal(['USD']); + }); + + it('should filter bidderConfig data', function () { + let validated; + let conf = { + bidders: ['bidderA', 'bidderB'], + config: { + ortb2: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 4}], + name: 't' + }] + } + } + } + }; + + config.setBidderConfig(conf); + + init(); + + validated = config.getBidderConfig(); + expect(validated.bidderA.ortb2).to.not.be.undefined; + expect(validated.bidderA.ortb2.user.data).to.be.undefined; + expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); + expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); + expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + }); + + it('should not filter bidderConfig data as it is valid', function () { + let validated; + let conf = { + bidders: ['bidderA', 'bidderB'], + config: { + ortb2: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'data1_id'}], + name: 'data1' + }] + } + } + } + }; + + config.setBidderConfig(conf); + + init(); + + validated = config.getBidderConfig(); + expect(validated.bidderA.ortb2).to.not.be.undefined; + expect(validated.bidderA.ortb2.user.data).to.deep.equal([{segment: [{id: 'data1_id'}], name: 'data1'}]); + expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); + expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); + expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + }); + + it('should not set default values if skipEnrichments is turned on', function () { + let validated; + config.setConfig({'firstPartyData': {skipEnrichments: true}}); + + let conf = { + site: { + keywords: 'other' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'data1_id'}], + name: 'data1' + }] + } + } + ; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig(); + expect(validated.ortb2).to.not.be.undefined; + expect(validated.ortb2.device).to.be.undefined; + expect(validated.ortb2.site.ref).to.be.undefined; + expect(validated.ortb2.site.page).to.be.undefined; + expect(validated.ortb2.site.domain).to.be.undefined; + }); + + it('should not validate ortb2 data if skipValidations is turned on', function () { + let validated; + config.setConfig({'firstPartyData': {skipValidations: true}}); + + let conf = { + site: { + keywords: 'other' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'nonfiltered'}] + }] + } + } + ; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig(); + expect(validated.ortb2).to.not.be.undefined; + expect(validated.ortb2.user.data).to.deep.equal([{segment: [{id: 'nonfiltered'}]}]); + }); + }); +}); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 3047b635d13..a5b4bd2a03f 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -152,6 +152,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) @@ -226,6 +239,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) @@ -297,7 +323,10 @@ describe('freewheelSSP BidAdapter Test', () => { ' ' + ' Adswizz' + ' ' + - ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-12008' + + ' ' + + ' ' + + ' ' + ' ' + ' ' + ' ' + @@ -332,13 +361,17 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', ad: ad } ]; let result = spec.interpretResponse(response, request[0]); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains).to.deep.equal([]); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('should get correct bid response with formated ad', () => { @@ -355,13 +388,17 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', ad: formattedAd } ]; let result = spec.interpretResponse(response, request[0]); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains).to.deep.equal([]); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('handles nobid responses', () => { @@ -372,6 +409,7 @@ describe('freewheelSSP BidAdapter Test', () => { expect(result.length).to.equal(0); }); }); + describe('interpretResponseForVideo', () => { let bidRequests = [ { @@ -435,6 +473,12 @@ describe('freewheelSSP BidAdapter Test', () => { ' Adswizz' + ' ' + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + ' ' + ' ' + ' ' + @@ -469,6 +513,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', vastXml: response, mediaType: 'video', ad: ad @@ -476,8 +522,10 @@ describe('freewheelSSP BidAdapter Test', () => { ]; let result = spec.interpretResponse(response, request[0]); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains).to.deep.equal([]); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('should get correct bid response with formated ad', () => { @@ -494,6 +542,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', vastXml: response, mediaType: 'video', ad: formattedAd @@ -501,8 +551,10 @@ describe('freewheelSSP BidAdapter Test', () => { ]; let result = spec.interpretResponse(response, request[0]); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains).to.deep.equal([]); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('handles nobid responses', () => { diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js index cdaa1b5448a..35394df7d11 100644 --- a/test/spec/modules/gammaBidAdapter_spec.js +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -69,7 +69,8 @@ describe('gammaBidAdapter', function() { 'adid': '1515999070', 'dealid': 'gax-paj2qarjf2g', 'h': 250, - 'w': 300 + 'w': 300, + 'adomain': ['testdomain.com'] }] }] } @@ -87,7 +88,8 @@ describe('gammaBidAdapter', function() { 'currency': 'USD', 'netRevenue': true, 'ttl': 300, - 'ad': '' + 'ad': '', + 'meta': {'advertiserDomains': ['testdomain.com']} }]; let result = spec.interpretResponse(serverResponse); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 79f58470cb3..8d63a32ef4d 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec, helper} from 'modules/gamoshiBidAdapter.js'; import * as utils from 'src/utils.js'; import {newBidder} from '../../../src/adapters/bidderFactory.js'; +import {deepClone} from 'src/utils'; const supplyPartnerId = '123'; const adapter = newBidder(spec); @@ -240,6 +241,40 @@ describe('GamoshiAdapter', () => { 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); + + const getFloorResponse = {currency: 'USD', floor: 5}; + let testBidRequest = deepClone(bidRequest); + let request = spec.buildRequests([testBidRequest], bidRequest)[0]; + + // 1. getBidFloor not exist AND bidfloor not exist - return 0 + let payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.equal(0); + + // 2. getBidFloor not exist AND bidfloor exist - use bidfloor property + testBidRequest = deepClone(bidRequest); + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(0.3) + + // 3. getBidFloor exist AND bidfloor not exist - use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) + + // 4. getBidFloor exist AND bidfloor exist -> use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) }); it('should validate adpos', () => { @@ -339,19 +374,42 @@ describe('GamoshiAdapter', () => { it('builds request video object correctly', () => { let response; const bidRequestWithVideo = utils.deepClone(bidRequest); + + bidRequestWithVideo.params.video = { + placement: 1, + minduration: 1, + } + bidRequestWithVideo.mediaTypes = { video: { - playerSize: [[302, 252]] + playerSize: [[302, 252]], + mimes: ['video/mpeg'], + playbackmethod: 1, + startdelay: 1, } }; response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); expect(response.data.imp[0].video.pos).to.equal(0); + + expect(response.data.imp[0].video.mimes).to.equal(bidRequestWithVideo.mediaTypes.video.mimes); + expect(response.data.imp[0].video.skip).to.not.exist; + expect(response.data.imp[0].video.placement).to.equal(1); + expect(response.data.imp[0].video.minduration).to.equal(1); + expect(response.data.imp[0].video.playbackmethod).to.equal(1); + expect(response.data.imp[0].video.startdelay).to.equal(1); + bidRequestWithVideo.mediaTypes = { video: { - playerSize: [302, 252] - } + playerSize: [302, 252], + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, + }, }; const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); @@ -367,7 +425,13 @@ describe('GamoshiAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes = { video: { - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, } }; let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; @@ -390,7 +454,13 @@ describe('GamoshiAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes.video = { playerSize: [[304, 254], [305, 255]], - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, }; response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; @@ -423,7 +493,7 @@ describe('GamoshiAdapter', () => { it('build request with ID5 Id', () => { const bidRequestClone = utils.deepClone(bidRequest); bidRequestClone.userId = {}; - bidRequestClone.userId.id5id = 'id5-user-id'; + bidRequestClone.userId.id5id = { uid: 'id5-user-id' }; let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; expect(request.data.user.ext.eids).to.deep.equal([{ 'source': 'id5-sync.com', @@ -480,6 +550,7 @@ describe('GamoshiAdapter', () => { expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); expect(ad0.vastXml).to.be.an('undefined'); expect(ad0.vastUrl).to.be.an('undefined'); + expect(ad0.meta.advertiserDomains).to.be.equal(rtbResponse.seatbid[1].bid[0].adomain); }); it('aggregates video bids from all seat bids', () => { diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 7f4828267a9..82cb70f42be 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,11 +1,24 @@ -import { deviceAccessHook, setEnforcementConfig, userSyncHook, userIdHook } from 'modules/gdprEnforcement.js'; +import { + deviceAccessHook, + setEnforcementConfig, + userSyncHook, + userIdHook, + makeBidRequestsHook, + validateRules, + enforcementRules, + purpose1Rule, + purpose2Rule, + enableAnalyticsHook, + getGvlid, + internal +} from 'modules/gdprEnforcement.js'; import { config } from 'src/config.js'; import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { validateStorageEnforcement } from 'src/storageManager.js'; -import { executeStorageCallbacks } from 'src/prebid.js'; +import events from 'src/events.js'; -describe('gdpr enforcement', function() { +describe('gdpr enforcement', function () { let nextFnSpy; let logWarnSpy; let gdprDataHandlerStub; @@ -34,11 +47,12 @@ describe('gdpr enforcement', function() { 'consents': { '1': true, '2': true, - '3': true + '3': true, + '7': true }, 'legitimateInterests': { '1': false, - '2': false, + '2': true, '3': false } }, @@ -46,7 +60,9 @@ describe('gdpr enforcement', function() { 'consents': { '1': true, '2': true, - '3': false + '3': false, + '4': true, + '5': false }, 'legitimateInterests': { '1': false, @@ -81,23 +97,38 @@ describe('gdpr enforcement', function() { } }; - after(function() { - validateStorageEnforcement.getHooks({hook: deviceAccessHook}).remove(); - $$PREBID_GLOBAL$$.requestBids.getHooks({hook: executeStorageCallbacks}).remove(); + after(function () { + validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); + $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); + adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function() { - beforeEach(function() { + describe('deviceAccessHook', function () { + let adapterManagerStub; + + function getBidderSpec(gvlid) { + return { + getSpec: () => { + return { + gvlid + } + } + } + } + + beforeEach(function () { nextFnSpy = sinon.spy(); gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); + adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); }); - afterEach(function() { + afterEach(function () { config.resetConfig(); gdprDataHandler.getConsentData.restore(); logWarnSpy.restore(); + adapterManagerStub.restore(); }); - it('should not allow device access when device access flag is set to false', function() { + it('should not allow device access when device access flag is set to false', function () { config.setConfig({ deviceAccess: false, consentManagement: { @@ -118,10 +149,12 @@ describe('gdpr enforcement', function() { hasEnforcementHook: true, valid: false } - expect(nextFnSpy.calledWith(undefined, result)); + sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); }); - it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function() { + it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); setEnforcementConfig({ gdpr: { rules: [{ @@ -143,7 +176,9 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(0); }); - it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function() { + it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); setEnforcementConfig({ gdpr: { rules: [{ @@ -164,7 +199,8 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(1); }); - it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function() { + it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); setEnforcementConfig({ gdpr: { rules: [{ @@ -187,15 +223,83 @@ describe('gdpr enforcement', function() { hasEnforcementHook: true, valid: true } - expect(nextFnSpy.calledWith(undefined, result)); + sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + }); + + it('should use gvlMapping set by publisher', function() { + config.setConfig({ + 'gvlMapping': { + 'appnexus': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + }); + + it('should use gvl id of alias and not of parent', function() { + let curBidderStub = sinon.stub(config, 'getCurrentBidder'); + curBidderStub.returns('appnexus-alias'); + adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); + config.setConfig({ + 'gvlMapping': { + 'appnexus-alias': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + curBidderStub.restore(); }); }); - describe('userSyncHook', function() { + describe('userSyncHook', function () { let curBidderStub; let adapterManagerStub; - beforeEach(function() { + beforeEach(function () { gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); curBidderStub = sinon.stub(config, 'getCurrentBidder'); @@ -203,7 +307,7 @@ describe('gdpr enforcement', function() { nextFnSpy = sinon.spy(); }); - afterEach(function() { + afterEach(function () { config.getCurrentBidder.restore(); config.resetConfig(); gdprDataHandler.getConsentData.restore(); @@ -211,7 +315,7 @@ describe('gdpr enforcement', function() { logWarnSpy.restore(); }); - it('should allow bidder to do user sync if consent is true', function() { + it('should allow bidder to do user sync if consent is true', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -230,7 +334,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -240,7 +344,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -250,7 +354,7 @@ describe('gdpr enforcement', function() { expect(nextFnSpy.calledTwice).to.equal(true); }); - it('should not allow bidder to do user sync if user has denied consent', function() { + it('should not allow bidder to do user sync if user has denied consent', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -269,7 +373,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -279,7 +383,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -290,7 +394,7 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(1); }); - it('should not check vendor consent when enforceVendor is false', function() { + it('should not check vendor consent when enforceVendor is false', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -309,7 +413,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -319,7 +423,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -331,16 +435,16 @@ describe('gdpr enforcement', function() { }); }); - describe('userIdHook', function() { - beforeEach(function() { + describe('userIdHook', function () { + beforeEach(function () { logWarnSpy = sinon.spy(utils, 'logWarn'); nextFnSpy = sinon.spy(); }); - afterEach(function() { + afterEach(function () { config.resetConfig(); logWarnSpy.restore(); }); - it('should allow user id module if consent is given', function() { + it('should allow user id module if consent is given', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -366,9 +470,10 @@ describe('gdpr enforcement', function() { const args = nextFnSpy.getCalls()[0].args; expect(args[1].hasValidated).to.be.true; expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); }); - it('should allow userId module if gdpr not in scope', function() { + it('should allow userId module if gdpr not in scope', function () { let submodules = [{ submodule: { gvlid: 1, @@ -381,10 +486,10 @@ describe('gdpr enforcement', function() { const args = nextFnSpy.getCalls()[0].args; expect(args[1]).to.be.null; expect(nextFnSpy.calledOnce).to.equal(true); - expect(nextFnSpy.calledWith(undefined, submodules, consentData)); + sinon.assert.calledWith(nextFnSpy, submodules, consentData); }); - it('should not enforce if not apiVersion 2', function() { + it('should not allow user id module if user denied consent', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -397,57 +502,624 @@ describe('gdpr enforcement', function() { }); let consentData = {} consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 1; + consentData.apiVersion = 2; consentData.gdprApplies = true; + let submodules = [{ submodule: { gvlid: 1, name: 'sampleUserId' } + }, { + submodule: { + gvlid: 3, + name: 'sampleUserId1' + } }] userIdHook(nextFnSpy, submodules, consentData); - // Should not pass back hasValidated flag since version 1 - const args = nextFnSpy.getCalls()[0].args; - expect(args[1].hasValidated).to.be.undefined; - expect(args[0]).to.deep.equal(submodules); - expect(nextFnSpy.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(1); + let expectedSubmodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }] + sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); }); + }); + + describe('makeBidRequestsHook', function () { + let sandbox; + let adapterManagerStub; + let emitEventSpy; + + const MOCK_AD_UNITS = [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_1' // has consent + }, { + bidder: 'bidder_2' // doesn't have consent, but liTransparency is true. Bidder remains active. + }] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_2' + }, { + bidder: 'bidder_3' + }] + }]; - it('should not allow user id module if user denied consent', function() { + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getBidAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + emitEventSpy = sandbox.spy(events, 'emit'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); + }); + + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is established)', function () { setEnforcementConfig({ gdpr: { rules: [{ - purpose: 'storage', - enforcePurpose: false, + purpose: 'basicAds', + enforcePurpose: true, enforceVendor: true, vendorExceptions: [] }] } }); - let consentData = {} + const consentData = {}; consentData.vendorData = staticConfig.consentData.getTCData; consentData.apiVersion = 2; consentData.gdprApplies = true; - let submodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } + } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } } + }); + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), + sinon.match({ bidder: 'bidder_2' }) + ] }, { - submodule: { - gvlid: 3, - name: 'sampleUserId1' + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_2' }), + sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. + ] + }], []); + }); + + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['bidder_3'] + }] } - }] - userIdHook(nextFnSpy, submodules, consentData); - expect(logWarnSpy.callCount).to.equal(1); - let expectedSubmodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' + }); + const consentData = {}; + + // set li for purpose 2 to false + const newConsentData = utils.deepClone(staticConfig); + newConsentData.consentData.getTCData.purpose.legitimateInterests['2'] = false; + + consentData.vendorData = newConsentData.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } + } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } } + }); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent + ] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions + ] + }], []); + + expect(logWarnSpy.calledOnce).to.equal(true); + }); + + it('should skip validation checks if GDPR version is not equal to "2"', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforePurpose: false, + enforceVendor: false, + vendorExceptions: [] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 1; + consentData.gdprApplies = true; + gdprDataHandlerStub.returns(consentData); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, sinon.match.array.deepEquals(MOCK_AD_UNITS), []); + expect(emitEventSpy.notCalled).to.equal(true); + expect(logWarnSpy.notCalled).to.equal(true); + }); + }); + + describe('enableAnalyticsHook', function () { + let sandbox; + let adapterManagerStub; + + const MOCK_ANALYTICS_ADAPTER_CONFIG = [{ + provider: 'analyticsAdapter_A', + options: {} + }, { + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getAnalyticsAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + }); + + afterEach(function() { + config.resetConfig(); + sandbox.restore(); + }); + + it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'measurement', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['analyticsAdapter_B'] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); + adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); + adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + + enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]); + expect(logWarnSpy.calledOnce).to.equal(true); + }); + }); + + describe('validateRules', function () { + const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = []) => ({ + purpose: purposeName, + enforcePurpose: enforcePurpose, + enforceVendor: enforceVendor, + vendorExceptions: vendorExceptions + }); + + const consentData = { + vendorData: staticConfig.consentData.getTCData, + apiVersion: 2, + gdprApplies: true + }; + + // Bidder - 'bidderA' has vendorConsent + const vendorAllowedModule = 'bidderA'; + const vendorAllowedGvlId = 1; + + // Bidder = 'bidderB' doesn't have vendorConsent + const vendorBlockedModule = 'bidderB'; + const vendorBlockedGvlId = 3; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['1'] = false; + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' and 'enforceVendor' both are 'true' + const gdprRule = createGdprRule('storage', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', true, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true' because vendorConsent doens't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' because vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('storage', false, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', false, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + it('should return true when "vendorExceptions" contains the name of the vendor under test', function () { + // 'vendorExceptions' contains 'bidderB' which doesn't have vendor consent. + const gdprRule = createGdprRule('storage', false, true, [vendorBlockedModule]); + + /* 'bidderB' gets a free pass since it's included in the 'vendorExceptions' array. validateRules must disregard + user's choice for purpose and vendor consent and return 'true' for this bidder(s) */ + const isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + describe('Purpose 2 special case', function () { + const consentDataWithLIFalse = utils.deepClone(consentData); + consentDataWithLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['2'] = false; + + const consentDataWithPurposeConsentFalseAndLIFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.consents['2'] = false; + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // both 'enforcePurpose' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true', but legitimateInterests for purpose 2 is 'false'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false', but legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'true' and vendor consent is 'true', as well as legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Purpose consent is 'true' and vendor consent is 'false', and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===false) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('basicAds', true, false, []); + + // case 1 - Purpose consent is 'true', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalseAndLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=false AND enforceVendor[p,v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', false, true, []); + + // case - 1 Vendor consent is 'true', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + }); + }) + + describe('setEnforcementConfig', function () { + let sandbox; + const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }]; + beforeEach(function () { + sandbox = sinon.createSandbox(); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); + }); + + it('should enforce TCF2 Purpose1 and Purpose 2 if no "rules" found in the config', function () { + setEnforcementConfig({ + gdpr: { + cmpApi: 'iab', + allowAuctionWithoutConsent: true, + timeout: 5000 + } + }); + + expect(logWarnSpy.calledOnce).to.equal(true); + expect(enforcementRules).to.deep.equal(DEFAULT_RULES); + }); + + it('should enforce TCF2 Purpose 2 also if only Purpose 1 is defined in "rules"', function () { + const purpose1RuleDefinedInConfig = { + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose1RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(purpose1RuleDefinedInConfig); + expect(purpose2Rule).to.deep.equal(DEFAULT_RULES[1]); + }); + + it('should enforce TCF2 Purpose 1 also if only Purpose 2 is defined in "rules"', function () { + const purpose2RuleDefinedInConfig = { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose2RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(DEFAULT_RULES[0]); + expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); + }); + + it('should use the "rules" defined in config if a definition found', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false + }] + setEnforcementConfig({gdpr: { rules }}); + + expect(enforcementRules).to.deep.equal(rules); + }); + }); + + describe('TCF2FinalResults', function() { + let sandbox; + beforeEach(function() { + sandbox = sinon.createSandbox(); + sandbox.spy(events, 'emit'); + }); + afterEach(function() { + config.resetConfig(); + sandbox.restore(); + }); + it('should emit TCF2 enforcement data on auction end', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false }] - expect(nextFnSpy.calledWith(undefined, expectedSubmodules, consentData)); + setEnforcementConfig({gdpr: { rules }}); + + events.emit('auctionEnd', {}) + + // Assertions + sinon.assert.calledWith(events.emit.getCall(1), 'tcf2Enforcement', sinon.match.object); + }) + }); + + describe('getGvlid', function() { + let sandbox; + let getGvlidForBidAdapterStub; + let getGvlidForUserIdModuleStub; + let getGvlidForAnalyticsAdapterStub; + beforeEach(function() { + sandbox = sinon.createSandbox(); + getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); + getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); + getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + }); + afterEach(function() { + sandbox.restore(); + config.resetConfig(); + }); + + it('should return "null" if called without passing any argument', function() { + const gvlid = getGvlid(); + expect(gvlid).to.equal(null); + }); + + it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); + + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(null); + }); + + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + moduleA: 1 + } + }); + + // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. + getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); + + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(1); + }); + + it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + + expect(getGvlid('moduleA')).to.equal(7); }); }); }); diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js new file mode 100644 index 00000000000..cf4e0b53fde --- /dev/null +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -0,0 +1,111 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; +import { server } from '../../../test/mocks/xhr.js'; + +let key = '123123123'; +function makeConfig() { + return { + name: 'geoedge', + params: { + wap: false, + key: key, + bidders: { + bidderA: true, + bidderB: false + } + } + }; +} + +function mockBid(bidderCode) { + return { + 'ad': '', + 'cpm': '1.00', + 'width': 300, + 'height': 250, + 'bidderCode': bidderCode, + 'requestId': utils.getUniqueIdentifierStr(), + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; +} + +let mockWrapper = `${htmlPlaceholder}`; + +describe('Geoedge RTD module', function () { + describe('beforeInit', function () { + let submoduleStub; + + before(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + }); + after(function () { + submoduleStub.restore(); + }); + it('should fetch the wrapper', function () { + beforeInit(); + let request = server.requests[0]; + let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; + expect(isWrapperRequest).to.equal(true); + }); + it('should register RTD submodule provider', function () { + expect(submoduleStub.calledWith('realTimeData', geoedgeSubmodule)).to.equal(true); + }); + }); + describe('setWrapper', function () { + it('should set the wrapper', function () { + setWrapper(mockWrapper); + expect(wrapper).to.equal(mockWrapper); + }); + }); + describe('submodule', function () { + describe('name', function () { + it('should be geoedge', function () { + expect(geoedgeSubmodule.name).to.equal('geoedge'); + }); + }); + describe('init', function () { + let insertElementStub; + + before(function () { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + after(function () { + utils.insertElement.restore(); + }); + it('should return false when missing params or key', function () { + let missingParams = geoedgeSubmodule.init({}); + let missingKey = geoedgeSubmodule.init({ params: {} }); + expect(missingParams || missingKey).to.equal(false); + }); + it('should return true when params are ok', function () { + expect(geoedgeSubmodule.init(makeConfig())).to.equal(true); + }); + it('should preload the client', function () { + let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href === getClientUrl(key); + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); + }); + }); + describe('onBidResponseEvent', function () { + let bidFromA = mockBid('bidderA'); + it('should wrap bid html when bidder is configured', function () { + geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig()); + expect(bidFromA.ad.indexOf('')).to.equal(0); + }); + it('should not wrap bid html when bidder is not configured', function () { + let bidFromB = mockBid('bidderB'); + geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig()); + expect(bidFromB.ad.indexOf('')).to.equal(-1); + }); + it('should only muatate the bid ad porperty', function () { + let copy = Object.assign({}, bidFromA); + delete copy.ad; + let equalsOriginal = Object.keys(copy).every(key => copy[key] === bidFromA[key]); + expect(equalsOriginal).to.equal(true); + }); + }); + }); +}); diff --git a/test/spec/modules/getintentBidAdapter_spec.js b/test/spec/modules/getintentBidAdapter_spec.js index 1959bda5c39..bb0b5ba826c 100644 --- a/test/spec/modules/getintentBidAdapter_spec.js +++ b/test/spec/modules/getintentBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai' import { spec } from 'modules/getintentBidAdapter.js' +import {deepClone} from 'src/utils'; describe('GetIntent Adapter Tests:', function () { const bidRequests = [{ @@ -17,8 +18,41 @@ describe('GetIntent Adapter Tests:', function () { tid: 't1000' }, sizes: [[50, 50], [100, 100]] - }] + }]; const videoBidRequest = { + mediaTypes: { + video: { + protocols: [1, 2, 3], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + minbitrate: 500, + maxbitrate: 1000, + api: [2], + skip: 1 + } + }, + bidId: 'bid789', + params: { + pid: 'p1001', + tid: 't1001', + }, + sizes: [300, 250], + mediaType: 'video' + }; + const videoBidRequestWithVideoParams = { + mediaTypes: { + video: { + protocols: [1, 2, 3], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + minbitrate: 500, + maxbitrate: 1000, + api: [2], + skip: 0 + } + }, bidId: 'bid789', params: { pid: 'p1001', @@ -27,7 +61,7 @@ describe('GetIntent Adapter Tests:', function () { mimes: ['video/mp4', 'application/javascript'], max_dur: 20, api: [1, 2], - skippable: true + skippable: 'ALLOW' } }, sizes: [300, 250], @@ -58,10 +92,54 @@ describe('GetIntent Adapter Tests:', function () { expect(serverRequest.data.tid).to.equal('t1001'); expect(serverRequest.data.size).to.equal('300x250'); expect(serverRequest.data.is_video).to.equal(true); + expect(serverRequest.data.protocols).to.equal('1,2,3'); + expect(serverRequest.data.mimes).to.equal('video/mp4'); + expect(serverRequest.data.min_dur).to.equal(5); + expect(serverRequest.data.max_dur).to.equal(30); + expect(serverRequest.data.min_btr).to.equal(500); + expect(serverRequest.data.max_btr).to.equal(1000); + expect(serverRequest.data.api).to.equal('2'); + expect(serverRequest.data.skippable).to.equal('ALLOW'); + }); + + it('Verify build video request with video params', function () { + const serverRequests = spec.buildRequests([videoBidRequestWithVideoParams]); + let serverRequest = serverRequests[0]; + expect(serverRequest.url).to.equal('https://px.adhigh.net/rtb/direct_vast'); + expect(serverRequest.method).to.equal('GET'); + expect(serverRequest.data.bid_id).to.equal('bid789'); + expect(serverRequest.data.pid).to.equal('p1001'); + expect(serverRequest.data.tid).to.equal('t1001'); + expect(serverRequest.data.size).to.equal('300x250'); + expect(serverRequest.data.is_video).to.equal(true); expect(serverRequest.data.mimes).to.equal('video/mp4,application/javascript'); expect(serverRequest.data.max_dur).to.equal(20); expect(serverRequest.data.api).to.equal('1,2'); - expect(serverRequest.data.skippable).to.equal(true); + expect(serverRequest.data.skippable).to.equal('ALLOW'); + }); + + it('Verify bid floor without price floors module', function() { + const bidRequestWithFloor = deepClone(bidRequests[0]); + bidRequestWithFloor.params.floor = 10 + bidRequestWithFloor.params.cur = 'USD' + + const serverRequests = spec.buildRequests([bidRequestWithFloor]); + let serverRequest = serverRequests[0]; + expect(serverRequest.data.cur).to.equal('USD'); + expect(serverRequest.data.floor).to.equal(10); + }); + + it('Verify bid floor with price floors module', function() { + const bidRequestWithFloor = deepClone(bidRequests[0]); + bidRequestWithFloor.params.floor = 10 + bidRequestWithFloor.params.cur = 'USD' + const getFloorResponse = {floor: 5, currency: 'EUR'}; + bidRequestWithFloor.getFloor = () => getFloorResponse; + + const serverRequests = spec.buildRequests([bidRequestWithFloor]); + let serverRequest = serverRequests[0]; + expect(serverRequest.data.cur).to.equal('EUR'); + expect(serverRequest.data.floor).to.equal(5); }); it('Verify parse response', function () { diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js new file mode 100644 index 00000000000..96bf319dfd2 --- /dev/null +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -0,0 +1,185 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gjirafaBidAdapter'; + +describe('gjirafaAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId or placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: {} + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'gjirafa', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://central.gjirafa.com/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('728x90'); + }); + }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://central.gjirafa.com/bid', + 'data': { + 'sizes': '728x90', + 'adUnitId': 'hb-leaderboard', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '
Test ad
', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360, + 'ADomain': ['somedomain.com'] + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad', + 'vastUrl', + 'mediaType', + 'meta' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function (key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + + it('all values correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal(728); + expect(result[0].height).to.equal(90); + expect(result[0].creativeId).to.equal('123abc'); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].netRevenue).to.equal(false); + expect(result[0].ttl).to.equal(360); + expect(result[0].referrer).to.equal('http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true'); + expect(result[0].ad).to.equal('
Test ad
'); + expect(result[0].meta.advertiserDomains).to.deep.equal(['somedomain.com']); + }) + }); +}); diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js index cc11efcb2af..336f606309e 100644 --- a/test/spec/modules/glimpseBidAdapter_spec.js +++ b/test/spec/modules/glimpseBidAdapter_spec.js @@ -61,7 +61,10 @@ const templateBidResponse = { }; const copyBidResponse = () => ({ ...templateBidResponse }); -const copyBidderRequest = () => ({ ...templateBidderRequest, bids: copyBidRequests() }); +const copyBidderRequest = () => ({ + ...templateBidderRequest, + bids: copyBidRequests(), +}); const copyBidRequest = () => ({ ...templateBidRequest }); const copyBidRequests = () => [copyBidRequest()]; @@ -139,7 +142,9 @@ describe('GlimpseProtocolAdapter', function () { expect(payload.gdprConsent).to.exist; const { gdprConsent } = payload; expect(gdprConsent.gdprApplies).to.be.true; - expect(gdprConsent.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(gdprConsent.consentString).to.equal( + bidderRequest.gdprConsent.consentString + ); }); it('should add referer info', function () { @@ -147,7 +152,9 @@ describe('GlimpseProtocolAdapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.refererInfo.referer).to.equal(templateBidderRequest.refererInfo.referer); + expect(payload.refererInfo.referer).to.equal( + templateBidderRequest.refererInfo.referer + ); }); }); @@ -175,5 +182,26 @@ describe('GlimpseProtocolAdapter', function () { const bids = spec.interpretResponse(response); expect(bids).to.have.length(0); }); + + it('should include advertiserDomains field in the response', function () { + const response = copyBidResponses(); + + const bids = spec.interpretResponse(response); + expect(bids[0].meta.advertiserDomains).to.be.an('array').that.is.empty; + }); + + it('should reflect the value of the OpenRTB adomain field', function () { + const advertiserDomainsMock = ['http://example.com']; + let response = copyBidResponses(); + response.body = response.body.map((bid) => { + return { + ...bid, + adomain: advertiserDomainsMock, + }; + }); + + const bids = spec.interpretResponse(response); + expect(bids[0].meta.advertiserDomains).to.equal(advertiserDomainsMock); + }); }); }); diff --git a/test/spec/modules/glomexBidAdapter_spec.js b/test/spec/modules/glomexBidAdapter_spec.js new file mode 100644 index 00000000000..6e5765c31f5 --- /dev/null +++ b/test/spec/modules/glomexBidAdapter_spec.js @@ -0,0 +1,135 @@ +import { expect } from 'chai' +import { spec } from 'modules/glomexBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +const REQUEST = { + bidder: 'glomex', + params: { + integrationId: 'abcdefg', + playlistId: 'defghjk' + }, + bidderRequestId: '143346cf0f1732', + auctionId: '2e41f65424c87c', + adUnitCode: 'adunit-code', + bidId: '2d925f27f5079f', + sizes: [640, 360] +} + +const BIDDER_REQUEST = { + auctionId: '2d921234f5079f', + refererInfo: { + isAmp: true, + numIframes: 0, + reachedTop: true, + referer: 'https://glomex.com' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CO5asbJO5asbJE-AAAENAACAAAAAAAAAAAYgAAAAAAAA.IAAA' + } +} + +const RESPONSE = { + bids: [ + { + id: '2d925f27f5079f', + cpm: 3.5, + width: 640, + height: 360, + creativeId: 'creative-1j75x4ln1kk6m1ius', + dealId: 'deal-1j75x4ln1kk6m1iut', + currency: 'EUR', + netRevenue: true, + ttl: 300, + ad: '', + adomain: ['glomex.com'] + } + ] +} +describe('glomexBidAdapter', 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 true when required params found', function () { + const request = { + 'params': { + 'integrationId': 'abcdefg' + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + }) + + describe('buildRequests', function () { + const bidRequests = [REQUEST] + const request = spec.buildRequests(bidRequests, BIDDER_REQUEST) + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST') + }) + + it('returns a list of valid requests', function () { + expect(request.validBidRequests).to.eql([REQUEST]) + }) + + it('sends params.integrationId', function () { + expect(request.validBidRequests[0].params.integrationId).to.eql(REQUEST.params.integrationId) + }) + + it('sends params.playlistId', function () { + expect(request.validBidRequests[0].params.playlistId).to.eql(REQUEST.params.playlistId) + }) + + it('sends refererInfo', function () { + expect(request.data.refererInfo).to.eql(BIDDER_REQUEST.refererInfo) + }) + + it('sends gdprConsent', function () { + expect(request.data.gdprConsent).to.eql(BIDDER_REQUEST.gdprConsent) + }) + + it('sends the auctionId', function () { + expect(request.data.auctionId).to.eql(BIDDER_REQUEST.auctionId) + }) + }) + + describe('interpretResponse', function () { + it('handles nobid responses', function () { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }) + + it('handles the server response', function () { + const result = spec.interpretResponse( + { + body: RESPONSE + }, + { + validBidRequests: [REQUEST] + } + ) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(3.5) + expect(result[0].width).to.equal(640) + expect(result[0].height).to.equal(360) + expect(result[0].creativeId).to.equal('creative-1j75x4ln1kk6m1ius') + expect(result[0].dealId).to.equal('deal-1j75x4ln1kk6m1iut') + expect(result[0].currency).to.equal('EUR') + expect(result[0].netRevenue).to.equal(true) + expect(result[0].ttl).to.equal(300) + expect(result[0].ad).to.equal('') + expect(result[0].meta.advertiserDomains).to.deep.equal(['glomex.com']) + }) + }) +}) diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 5de3db623c5..c22badb9b4c 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/gmosspBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import {getStorageManager} from 'src/storageManager'; import * as utils from 'src/utils.js'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; @@ -35,6 +36,7 @@ describe('GmosspAdapter', function () { }); describe('buildRequests', function () { + const storage = getStorageManager(); const bidRequests = [ { bidder: 'gmossp', @@ -54,17 +56,31 @@ describe('GmosspAdapter', function () { } ]; - const bidderRequest = { - refererInfo: { - referer: 'https://hoge.com' - } - }; - it('sends bid request to ENDPOINT via GET', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://hoge.com' + } + }; + storage.setCookie('_im_uid.1000283', 'h.0a4749e7ffe09fa6'); + 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('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); + expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&im_uid=h.0a4749e7ffe09fa6&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); + }); + + it('should use fallback if refererInfo.referer in bid request is empty and _im_uid.1000283 cookie is empty', function () { + const bidderRequest = { + refererInfo: { + referer: '' + } + }; + storage.setCookie('_im_uid.1000283', ''); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=' + encodeURIComponent(window.top.location.href) + '&cur=JPY&dnt=0&'; + expect(requests[0].data).to.equal(result); }); }); @@ -94,6 +110,9 @@ describe('GmosspAdapter', function () { price: 20, w: 300, h: 250, + adomains: [ + 'test.com' + ], ad: '
', creativeId: '985ec572b32be309.76973017', cur: 'JPY', @@ -114,6 +133,11 @@ describe('GmosspAdapter', function () { currency: 'JPY', width: 300, height: 250, + meta: { + advertiserDomains: [ + 'test.com' + ] + }, ad: '
', creativeId: '985ec572b32be309.76973017', netRevenue: true, diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js new file mode 100644 index 00000000000..eeb33418a82 --- /dev/null +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/gnetBidAdapter.js'; +import { + newBidder +} from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; + +describe('gnetAdapter', 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: 'gnet', + params: { + websiteId: '4' + } + }; + + 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 = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + bidder: 'gnet', + params: { + websiteId: '4' + }, + adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', + sizes: [ + [300, 250], + ], + bidId: '2a19afd5173318', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5' + }]; + + const bidderRequest = { + refererInfo: { + referer: 'https://gnetproject.com/' + } + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].data).to.equal(JSON.stringify({ + 'referer': 'https://gnetproject.com/', + 'adUnitCode': '/150790500/4_ZONA_IAB_300x250_5', + 'bidId': '2a19afd5173318', + 'transactionId': '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + 'sizes': ['300x250'], + 'params': { + 'websiteId': '4' + } + })); + }); + }); + + describe('interpretResponse', function () { + const bidderRequests = [{ + bidder: 'gnet', + params: { + clientId: '123456' + }, + adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', + sizes: [ + [300, 250], + ], + bidId: '2a19afd5173318', + bidderRequestId: '1f4001782ac16c', + auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', + transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5' + }]; + + it('should get correct banner bid response', function () { + const response = { + bids: [ + { + bidId: '2a19afd5173318', + cpm: 0.1, + currency: 'BRL', + width: 300, + height: 250, + ad: '

I am an ad

', + creativeId: '173560700', + } + ] + }; + + const expectedResponse = [ + { + requestId: '2a19afd5173318', + cpm: 0.1, + currency: 'BRL', + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 300, + meta: { + advertiserDomains: [] + }, + creativeId: '173560700', + netRevenue: true + } + ]; + + const result = spec.interpretResponse({ + body: response + }, bidderRequests); + expect(result).to.have.lengthOf(1); + expect(result).to.deep.have.same.members(expectedResponse); + }); + + it('handles nobid responses', function () { + const response = ''; + + const result = spec.interpretResponse({ + body: response + }, bidderRequests); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/gothamadsBidAdapter_spec.js b/test/spec/modules/gothamadsBidAdapter_spec.js new file mode 100644 index 00000000000..f0a3ea253f3 --- /dev/null +++ b/test/spec/modules/gothamadsBidAdapter_spec.js @@ -0,0 +1,416 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gothamadsBidAdapter.js'; +import { config } from 'src/config.js'; + +const NATIVE_BID_REQUEST = { + code: 'native_example', + 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: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +}; + +const BANNER_BID_REQUEST = { + code: 'banner_example', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000, + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' +} + +const bidRequest = { + refererInfo: { + referer: 'test.com' + } +} + +const VIDEO_BID_REQUEST = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { + video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +} + +const BANNER_BID_RESPONSE = { + 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 VIDEO_BID_RESPONSE = { + 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', + vastUrl: 'http://example.vast', + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const NATIVE_BID_RESPONSE = { + 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: 0, + 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('GothamAdsAdapter', function () { + describe('with COPPA', function () { + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, NATIVE_BID_REQUEST); + delete bid.params; + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], 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://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + + 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([BANNER_BID_REQUEST]); + + 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('check consent and ccpa string is set properly', function () { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }); + + 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://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST]); + + 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://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + 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: [BANNER_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: BANNER_BID_RESPONSE.id, + cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, + width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, + height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: BANNER_BID_RESPONSE.ttl || 1200, + currency: BANNER_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'banner', + meta: BANNER_BID_RESPONSE.seatbid[0].bid[0].adomain, + ad: BANNER_BID_RESPONSE.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', 'meta'); + 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.meta).to.have.property('advertiserDomains', expectedBidResponse.meta); + 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: [VIDEO_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: VIDEO_BID_RESPONSE.id, + cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, + width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, + height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: VIDEO_BID_RESPONSE.ttl || 1200, + currency: VIDEO_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'video', + meta: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adomain, + vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, + vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl + } + + 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', 'vastXml', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + 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).to.have.property('advertiserDomains', expectedBidResponse.meta); + 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: [NATIVE_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: NATIVE_BID_RESPONSE.id, + cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, + width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, + height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: NATIVE_BID_RESPONSE.ttl || 1200, + currency: NATIVE_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, + meta: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adomain, + mediaType: 'native', + native: { + clickUrl: NATIVE_BID_RESPONSE.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', 'meta'); + 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.meta).to.have.property('advertiserDomains', expectedBidResponse.meta); + 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/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js new file mode 100644 index 00000000000..3e8dbfe8d92 --- /dev/null +++ b/test/spec/modules/gptPreAuction_spec.js @@ -0,0 +1,216 @@ +import { + appendGptSlots, + appendPbAdSlot, + _currentConfig, + makeBidRequestsHook +} from 'modules/gptPreAuction.js'; +import { config } from 'src/config.js'; +import { makeSlot } from '../integration/faker/googletag.js'; + +describe('GPT pre-auction module', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + config.setConfig({ gptPreAuction: { enabled: false } }); + }); + + const testSlots = [ + makeSlot({ code: 'slotCode1', divId: 'div1' }), + makeSlot({ code: 'slotCode2', divId: 'div2' }), + makeSlot({ code: 'slotCode3', divId: 'div3' }) + ]; + + describe('appendPbAdSlot', () => { + // sets up our document body to test the pbAdSlot dom actions against + document.body.innerHTML = '
test1
' + + '
test2
' + + '
test2
'; + + it('should be unchanged if already defined on adUnit', () => { + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; + appendPbAdSlot(adUnit); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); + }); + + it('should use adUnit.code if matching id exists', () => { + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; + appendPbAdSlot(adUnit); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); + }); + + it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; + appendPbAdSlot(adUnit); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); + }); + + it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; + adUnit.code = 'foo5'; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); + }); + + it('should use the adUnit.code if all other sources failed', () => { + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); + }); + + it('should use the customPbAdSlot function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customPbAdSlot: () => 'customPbAdSlotName' + } + }); + + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; + appendPbAdSlot(adUnit); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); + }); + }); + + describe('appendGptSlots', () => { + it('should not add adServer object to context if no slots defined', () => { + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; + }); + + it('should not add adServer object to context if no slot matches', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; + }); + + it('should add adServer object to context if matching slot is found', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); + }); + + it('will trim child id if mcmEnabled is set to true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), + makeSlot({ code: '/12345,21212/slotCode2', divId: 'div2' }), + makeSlot({ code: '/12345,21212/slotCode3', divId: 'div3' }) + ]); + const adUnit = { code: '/12345,21212/slotCode2', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345/slotCode2' }); + }); + + it('will not trim child id if mcmEnabled is not set to true', () => { + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), + makeSlot({ code: '/12345,21212/slotCode2', divId: 'div2' }), + makeSlot({ code: '/12345,21212/slotCode3', divId: 'div3' }) + ]); + const adUnit = { code: '/12345,21212/slotCode2', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345,21212/slotCode2' }); + }); + + it('should use the customGptSlotMatching function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: slot => + adUnitCode => adUnitCode.toUpperCase() === slot.getAdUnitPath().toUpperCase() + } + }); + + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; + appendGptSlots([adUnit]); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); + }); + }); + + describe('handleSetGptConfig', () => { + it('should enable the module by default', () => { + config.setConfig({ gptPreAuction: {} }); + expect(_currentConfig.enabled).to.equal(true); + }); + + it('should disable the module if told to in set config', () => { + config.setConfig({ gptPreAuction: { enabled: false } }); + expect(_currentConfig).to.be.an('object').that.is.empty; + }); + + it('should accept custom functions in config', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: () => 'customGptSlot', + customPbAdSlot: () => 'customPbAdSlot' + } + }); + + expect(_currentConfig.enabled).to.equal(true); + expect(_currentConfig.customGptSlotMatching).to.a('function'); + expect(_currentConfig.customPbAdSlot).to.a('function'); + expect(_currentConfig.customGptSlotMatching()).to.equal('customGptSlot'); + expect(_currentConfig.customPbAdSlot()).to.equal('customPbAdSlot'); + }); + + it('should check that custom functions in config are type function', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: 12345, + customPbAdSlot: 'test' + } + }); + expect(_currentConfig).to.deep.equal({ + enabled: true, + customGptSlotMatching: false, + customPbAdSlot: false + }); + }); + }); + + describe('makeBidRequestsHook', () => { + let returnedAdUnits; + const runMakeBidRequests = adUnits => { + const next = adUnits => { + returnedAdUnits = adUnits; + }; + makeBidRequestsHook(next, adUnits); + }; + + it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { + const testAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, { + code: 'slotCode1', + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } + }, { + code: 'slotCode3', + }]; + + const expectedAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, { + code: 'slotCode1', + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } + }, { + code: 'slotCode3', + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index c8d04113aeb..7913dd7b44d 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/gridBidAdapter.js'; +import { spec, resetUserSync, getSyncUrl, storage } from 'modules/gridBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; @@ -40,123 +40,302 @@ describe('TheMediaGrid Adapter', 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; + function parseRequest(data) { + return JSON.parse(data); } - const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; - const referrer = bidderRequest.refererInfo.referer; + const bidderRequest = { + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: '22edbae2733bf6', + auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + timeout: 3000 + }; + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); let bidRequests = [ { 'bidder': 'grid', 'params': { - 'uid': '1' + 'uid': '1', + 'bidFloor': 1.25 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', }, { 'bidder': 'grid', 'params': { - 'uid': '1' + 'uid': '11' }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], 'mediaTypes': { 'video': { - 'playerSize': [400, 600] - }, - 'banner': { - 'sizes': [[728, 90]] + 'playerSize': [[400, 600]], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] } }, 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', }, { 'bidder': 'grid', 'params': { - 'uid': '2' + 'uid': '3' }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], 'mediaTypes': { 'video': { - 'playerSize': [400, 600] + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] }, 'banner': { - 'sizes': [[300, 250], [300, 600]] + 'sizes': [[728, 90]] } }, - 'bidId': '42dbe3a7168a6a', + 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', } ]; it('should attach valid params to the tag', function () { + const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdVal : null); + 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('auids', '1'); - 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$'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': fpdUserIdVal + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + + getDataFromLocalStorageStub.restore(); }); - it('sizes must be added from mediaTypes', function () { + it('make possible to process request without mediaTypes', function () { + const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdVal : null); + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], 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('auids', '1,1'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': fpdUserIdVal + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); - 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('auids', '1,1,2'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); + getDataFromLocalStorageStub.restore(); }); - it('if gdprConsent is present payload must have gdpr params', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + it('should attach valid params to the video tag', function () { + const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdVal : null); + + const request = spec.buildRequests(bidRequests.slice(0, 3), 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('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': fpdUserIdVal + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }] + }); + + getDataFromLocalStorageStub.restore(); }); - it('if gdprApplies is false gdpr_applies must be 0', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + it('should support mixed mediaTypes', function () { + const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdVal : null); + + const request = spec.buildRequests(bidRequests, bidderRequest); 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'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': fpdUserIdVal + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600, + 'protocols': [1, 2, 3] + } + }] + }); + + getDataFromLocalStorageStub.restore(); }); - it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + 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); 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'); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('consent', 'AAA'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('gdpr', 1); }); it('if usPrivacy is present payload must have us_privacy param', function () { @@ -164,110 +343,344 @@ describe('TheMediaGrid Adapter', function () { 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'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).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], + it('if userId is present payload must have user.ext param with right keys', function () { + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { - params: { - uid: '1', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [3], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' } - } + }] } - ); + ]; + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userIdAsEids: eids + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext.eids).to.deep.equal(eids); + }); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + it('if schain is present payload must have source.ext.schain param', function () { + const schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const bidRequestsWithSChain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithSChain, 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).to.have.property('source'); + expect(payload.source).to.have.property('ext'); + expect(payload.source.ext).to.have.property('schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); + }); - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['3'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' + it('if content and segment is present in jwTargeting, payload must have right params', function () { + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { + return Object.assign({ + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } + } + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithJwTargeting, 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: 'iow_labs_pub_data', + segment: [ + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} + ] }]); + expect(payload).to.have.property('site'); + expect(payload.site.content).to.deep.equal(jsContent); }); - it('should mix keyword param with keywords from config', function () { + it('should contain the keyword values if it present in ortb2.(site/user)', function () { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'fpd.user' ? {'keywords': ['a', 'b']} : arg === 'fpd.context' ? {'keywords': ['any words']} : null); - - const bidRequestWithKeywords = [].concat(bidRequests); - bidRequestWithKeywords[1] = Object.assign({}, - bidRequests[1], - { - params: { - uid: '1', - keywords: { - single: 'val', - singleArr: ['val'], - multiValMixed: ['value1', 2, 'value3'] + 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({ + '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); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(2000); + getConfigStub.restore(); + }); + 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); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(3000); + getConfigStub.restore(); + }); + it('should contain regs.coppa if coppa is true in config', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'coppa' ? true : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('coppa', 1); + 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 + }); + }); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + it('all id must be a string', function() { + const fpdUserIdNumVal = 2345543345; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdNumVal : null); + let bidRequestWithNumId = { + 'bidder': 'grid', + 'params': { + 'uid': 1, + }, + 'adUnitCode': 1233, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': 123123123, + 'bidderRequestId': 345345345, + 'auctionId': 654645, + }; + const bidderRequestWithNumId = { + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: 345345345, + auctionId: 654645, + timeout: 3000 + }; + const parsedReferrer = encodeURIComponent(bidderRequestWithNumId.refererInfo.referer); + const request = spec.buildRequests([bidRequestWithNumId], bidderRequestWithNumId); 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).to.deep.equal({ + 'id': '345345345', + 'site': { + 'page': parsedReferrer + }, + 'tmax': bidderRequestWithNumId.timeout, + 'source': { + 'tid': '654645', + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': '2345543345' + }, + 'imp': [{ + 'id': '123123123', + 'tagid': '1', + 'ext': {'divid': '1233'}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'user', - 'value': ['a', 'b'] - }, { - 'key': 'context', - 'value': ['any words'] - }]); + getDataFromLocalStorageStub.restore(); + }) - getConfigStub.restore(); + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + const bidRequest = Object.assign({ + getFloor: (_) => { + return floorTestData; + } + }, bidRequests[1]); + it('should return the value from getFloor if present', function () { + 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); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + 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); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + 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); + }); }); }); describe('interpretResponse', function () { const responses = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '1'}, + {'bid': [{'impid': '4dff80cc4ee346', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '5703af74d0472a', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '2344da98f78b42', 'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, @@ -298,11 +711,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, } ]; @@ -319,7 +734,7 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', + 'bidId': '659423fff799cb', 'bidderRequestId': '2c2bb1972df9a', 'auctionId': '1fa09aee5c8c99', }, @@ -349,18 +764,20 @@ describe('TheMediaGrid Adapter', function () { const request = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '300bfeb0d71a5b', + 'requestId': '659423fff799cb', 'cpm': 1.15, 'creativeId': 1, 'dealId': 11, 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, }, { 'requestId': '4dff80cc4ee346', @@ -370,11 +787,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, }, { 'requestId': '5703af74d0472a', @@ -384,11 +803,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, } ]; @@ -429,11 +850,45 @@ describe('TheMediaGrid Adapter', function () { 'context': 'instream' } } + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '13' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '23312a43bc42', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '14' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'a74b342f8cd', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } } ]; const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} + {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': '2bc598e42b6a', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'}, + {'bid': [{'impid': '23312a43bc42', 'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'auid': 13, content_type: 'video', 'adomain': ['example.com'], w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': 'a74b342f8cd', 'price': 1.50, 'nurl': '', 'auid': 14, content_type: 'video'}], 'seat': '2'} ]; const request = spec.buildRequests(bidRequests); const expectedResponse = [ @@ -444,15 +899,52 @@ describe('TheMediaGrid Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' } + }, + { + 'requestId': '2bc598e42b6a', + 'cpm': 1.00, + 'creativeId': 12, + 'dealId': undefined, + 'width': undefined, + 'height': undefined, + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + }, + { + 'requestId': '23312a43bc42', + 'cpm': 2.00, + 'creativeId': 13, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'meta': { + advertiserDomains: ['example.com'] + }, + 'vastUrl': 'https://some_test_vast_url.com', } ]; @@ -503,11 +995,11 @@ describe('TheMediaGrid Adapter', function () { it('complicated case', function () { const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, + {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, + {'bid': [{'impid': '26d6f897b516', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '326bde7fbf69', 'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '2234f233b22a', 'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, ]; const bidRequests = [ { @@ -576,11 +1068,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, }, { 'requestId': '4e111f1b66e4', @@ -590,11 +1084,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, }, { 'requestId': '26d6f897b516', @@ -604,11 +1100,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, }, { 'requestId': '326bde7fbf69', @@ -618,11 +1116,13 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, } ]; @@ -630,11 +1130,7 @@ describe('TheMediaGrid Adapter', function () { 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': 1, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, - ]; + it('response with ext.bidder.grid.demandSource', function () { const bidRequests = [ { 'bidder': 'grid', @@ -643,66 +1139,57 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], - 'bidId': '5126e301f4be', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57b2ebe70e16', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '225fcd44b18c', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'bidId': '26d6f897b516', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', } ]; + const serverResponse = { + 'bid': [ + { + 'impid': '26d6f897b516', + 'price': 1.15, + 'adm': '
test content 1
', + 'auid': 1, + 'h': 250, + 'w': 300, + 'dealid': 11, + 'ext': { + 'bidder': { + 'grid': { + 'demandSource': 'someValue' + } + } + } + } + ], + 'seat': '1' + }; const request = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '5126e301f4be', + 'requestId': '26d6f897b516', 'cpm': 1.15, 'creativeId': 1, - 'dealId': undefined, + 'dealId': 11, 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': false, - 'ttl': 360, - }, - { - 'requestId': '57b2ebe70e16', - 'cpm': 0.5, - 'creativeId': 1, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 2
', - 'bidderCode': 'grid', - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, + 'meta': { + advertiserDomains: [], + demandSource: 'someValue' + }, + 'adserverTargeting': { + 'hb_ds': 'someValue' + } } ]; - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + const result = spec.interpretResponse({'body': {'seatbid': [serverResponse]}}, request); expect(result).to.deep.equal(expectedResponse); }); }); diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..6d3a607c0c5 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -132,6 +132,43 @@ describe('TheMediaGridNM Adapter', function () { expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); + + it('should return true when required params is absent, but available in mediaTypes', function () { + const paramsList = [ + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + } + } + ]; + + const mediaTypes = { + video: { + mimes: ['video/mp4', 'video/x-ms-wmv'], + playerSize: [200, 300], + protocols: [1, 2, 3, 4, 5, 6] + } + }; + + paramsList.forEach((params) => { + const validBid = Object.assign({}, bid); + delete validBid.params; + validBid.params = params; + validBid.mediaTypes = mediaTypes; + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + }); }); describe('buildRequests', function () { @@ -198,6 +235,39 @@ describe('TheMediaGridNM Adapter', function () { }); }); + it('should attach valid params from mediaTypes', function () { + const mediaTypes = { + video: { + skipafter: 10, + minduration: 10, + maxduration: 100, + protocols: [1, 3, 4], + playerSize: [300, 250] + } + }; + const bidRequest = Object.assign({ mediaTypes }, bidRequests[0]); + const req = spec.buildRequests([bidRequest], bidderRequest)[0]; + const expectedVideo = { + 'skipafter': 10, + 'mind': 10, + 'maxd': 100, + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'size': '300x250' + }; + const expectedParams = Object.assign({}, bidRequest.params); + expectedParams.video = Object.assign(expectedParams.video, expectedVideo); + + expect(req.url).to.be.an('string'); + const payload = parseRequestUrl(req.url); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + expect(payload).to.have.property('sizes', '300x250,300x600'); + expect(req.data).to.deep.equal(expectedParams); + }); + it('if gdprConsent is present payload must have gdpr params', function () { const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); expect(request.url).to.be.an('string'); @@ -235,7 +305,7 @@ describe('TheMediaGridNM Adapter', function () { describe('interpretResponse', function () { const responses = [ {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, - {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300, adomain: ['my_domain.ru']}], 'seat': '2'}, {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, undefined, @@ -300,12 +370,14 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'meta': { + 'advertiserDomains': [] + }, 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' } @@ -317,12 +389,14 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': false, + 'netRevenue': true, 'ttl': 360, 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'meta': { + 'advertiserDomains': ['my_domain.ru'] + }, 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' } @@ -354,6 +428,9 @@ describe('TheMediaGridNM Adapter', function () { 'bidId': '2bc598e42b6a', 'bidderRequestId': '39d74f5b71464', 'auctionId': '1cbd2feafe5e8b', + 'meta': { + 'advertiserDomains': [] + }, 'mediaTypes': { 'video': { 'context': 'instream' diff --git a/test/spec/modules/growadvertisingBidAdapter_spec.js b/test/spec/modules/growadvertisingBidAdapter_spec.js new file mode 100644 index 00000000000..55eea06cca8 --- /dev/null +++ b/test/spec/modules/growadvertisingBidAdapter_spec.js @@ -0,0 +1,228 @@ +import { expect } from 'chai'; +import { spec } from 'modules/growadvertisingBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import {BANNER, NATIVE} from '../../../src/mediaTypes.js'; + +describe('GrowAdvertising Adapter', function() { + const ZONE_ID = 'unique-zone-id'; + const serverResponseBanner = { + body: { + status: 'success', + width: 300, + height: 250, + creativeId: 'ULqaukILu0RnMa0FyidOtkji4Po3qbgQ9ceRVGlhjLLKnrrLAATmGNCwtE99Ems8', + ad: '', + cpm: 1, + ttl: 180, + currency: 'USD', + type: BANNER, + } + }; + const serverResponseNative = { + body: { + status: 'success', + width: 400, + height: 300, + creativeId: 'ULqaukILu0RnMa0FyidOtkji4Po3qbgQ9ceRVGlhjLLKnrrLAATmGNCwtE99Ems9', + cpm: 2, + ttl: 180, + currency: 'USD', + native: { + title: 'Test title', + body: 'Test body', + body2: null, + sponsoredBy: 'Sponsored by', + cta: null, + clickUrl: 'https://example.org', + image: { + width: 400, + height: 300, + url: 'https://image.source.com/img', + } + }, + type: NATIVE + } + }; + let bidRequests = []; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + maxCPM: 5, + minCPM: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + }, + { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + }, + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + }, + }, + } + ]; + }); + + describe('implementation', function () { + describe('for requests', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'growads', + params: { + zoneId: ZONE_ID + } + }; + + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('should reject null zoneId bid', function () { + let zoneNullBid = { + bidder: 'growads', + params: { + zoneId: null + } + }; + + let isValid = spec.isBidRequestValid(zoneNullBid); + expect(isValid).to.equal(false); + }); + + it('should reject absent zoneId bid', function () { + let absentZoneBid = { + bidder: 'growads', + params: { + param: ZONE_ID + } + }; + + let isValid = spec.isBidRequestValid(absentZoneBid); + expect(isValid).to.equal(false); + }); + + it('should use custom domain', function () { + let validBid = { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + domain: 'test.subdomain.growadvertising.com', + }, + }; + + let requests = spec.buildRequests([validBid]); + expect(requests[0].url).to.have.string('test.subdomain.'); + }); + + it('should use default domain', function () { + let validBid = { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + }, + }; + + let requests = spec.buildRequests([validBid]); + expect(requests[0].url).to.have.string('portal.growadvertising.com'); + }); + + it('should increment zone index', function () { + let validBids = [ + { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + }, + }, + { + bidder: 'growads', + params: { + zoneId: ZONE_ID, + }, + } + ]; + + let requests = spec.buildRequests(validBids); + expect(requests[0].data).to.include({i: 0}); + expect(requests[1].data).to.include({i: 1}); + }); + }); + + describe('bid responses', function () { + describe(BANNER, function () { + it('should return complete bid response banner', function () { + let bids = spec.interpretResponse(serverResponseBanner, {bidRequest: bidRequests[0]}); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].bidderCode).to.equal('growads'); + expect(bids[0].cpm).to.equal(1); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].creativeId).to.have.length.above(1); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].mediaType).to.equal(BANNER); + }); + + it('should return empty bid on incorrect size', function () { + let response = utils.mergeDeep(serverResponseBanner, { + body: { + width: 150, + height: 150 + } + }); + + let bids = spec.interpretResponse(response, {bidRequest: bidRequests[0]}); + expect([]).to.be.lengthOf(0); + }); + + it('should return empty bid on incorrect CPM', function () { + let response = utils.mergeDeep(serverResponseBanner, { + body: { + cpm: 10 + } + }); + + let bids = spec.interpretResponse(response, {bidRequest: bidRequests[0]}); + expect([]).to.be.lengthOf(0); + }); + }); + + describe(NATIVE, function () { + it('should return complete bid response banner', function () { + let bids = spec.interpretResponse(serverResponseNative, {bidRequest: bidRequests[1]}); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].bidderCode).to.equal('growads'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].width).to.equal(400); + expect(bids[0].height).to.equal(300); + expect(bids[0].creativeId).to.have.length.above(1); + expect(bids[0]).property('native'); + expect(bids[0].native.title).to.have.length.above(1); + expect(bids[0].native.body).to.have.length.above(1); + expect(bids[0].native).property('image'); + expect(bids[0].mediaType).to.equal(NATIVE); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 7022f8f96dd..6c3b4eb680d 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,3 +1,5 @@ +import { BANNER, VIDEO } from 'src/mediaTypes.js'; + import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/gumgumBidAdapter.js'; @@ -33,7 +35,11 @@ describe('gumgumAdapter', function () { }; it('should return true when required params found', function () { + const zoneBid = { ...bid, params: { 'zone': '123' } }; + const pubIdBid = { ...bid, params: { 'pubId': 123 } }; expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(zoneBid)).to.equal(true); + expect(spec.isBidRequestValid(pubIdBid)).to.equal(true); }); it('should return true when required params found', function () { @@ -46,6 +52,17 @@ describe('gumgumAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when inslot sends sizes and trackingid', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789', + 'sizes': [[0, 1], [2, 3], [4, 5], [6, 7]] + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when no unit type is specified', function () { let bid = Object.assign({}, bid); delete bid.params; @@ -81,39 +98,205 @@ describe('gumgumAdapter', function () { }); describe('buildRequests', function () { + let sizesArray = [[300, 250], [300, 600]]; let bidRequests = [ { - 'bidder': 'gumgum', - 'params': { - 'inSlot': '9' + bidder: 'gumgum', + params: { + inSlot: 9 }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + mediaTypes: { + banner: { + sizes: sizesArray + } + }, + adUnitCode: 'adunit-code', + sizes: sizesArray, + bidId: '30b31c1838de1e', + schain: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' }, { - 'asi': 'exchange2.com', - 'sid': 'abcd', - 'hp': 1, - 'rid': 'bid-request-2', - 'name': 'intermediary', - 'domain': 'intermediary.com' + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + name: 'intermediary', + domain: 'intermediary.com' } ] } } ]; + const vidMediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 123456, + protocols: [1, 2] + } + }; + const zoneParam = { 'zone': '123a' }; + const pubIdParam = { 'pubId': 123 }; + + it('should set pubId param if found', function () { + const request = { ...bidRequests[0], params: pubIdParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pubId).to.equal(pubIdParam.pubId); + }); + + it('should set t param when zone param is found', function () { + const request = { ...bidRequests[0], params: zoneParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.t).to.equal(zoneParam.zone); + }); + + it('should send the banner dimension with the greatest width or height for slot ads', function () { + const legacyRequest = { ...bidRequests[0] }; + const slotZoneRequest = { ...bidRequests[0], params: { ...zoneParam, slot: 9 } } + const slotPubIdRequest = { ...bidRequests[0], params: { ...pubIdParam, slot: 9 } } + const legacyBidRequest = spec.buildRequests([legacyRequest])[0]; + const slotZoneBidRequest = spec.buildRequests([slotZoneRequest])[0]; + const slotPubIdBidRequest = spec.buildRequests([slotPubIdRequest])[0]; + expect(legacyBidRequest.data.maxw).to.equal(300); + expect(legacyBidRequest.data.maxh).to.equal(600); + expect(slotZoneBidRequest.data.maxw).to.equal(300); + expect(slotZoneBidRequest.data.maxh).to.equal(600); + expect(slotPubIdBidRequest.data.maxw).to.equal(300); + expect(slotPubIdBidRequest.data.maxh).to.equal(600); + }) + + it('should set the iriscat param when found', function () { + const request = { ...bidRequests[0], params: { iriscat: 'abc123' } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.have.property('iriscat'); + }); + + it('should not set the iriscat param when not found', function () { + const request = { ...bidRequests[0] } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('iriscat'); + }); + + it('should set the irisid param when found', function () { + const request = { ...bidRequests[0], params: { irisid: 'abc123' } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.have.property('irisid'); + }); + + it('should not set the irisid param when not found', function () { + const request = { ...bidRequests[0] } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('irisid'); + }); + + it('should not set the irisid param when not of type string', function () { + const request = { ...bidRequests[0], params: { irisid: 123456 } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('irisid'); + }); + + it('should set the global placement id (gpid)', function () { + const req = { ...bidRequests[0], ortb2Imp: { ext: { data: { adserver: { name: 'test', adslot: 123456 } } } } } + const bidRequest = spec.buildRequests([req])[0]; + expect(bidRequest.data).to.have.property('gpid'); + expect(bidRequest.data.gpid).to.equal(123456); + }); + + it('should set the bid floor if getFloor module is not present but static bid floor is defined', function () { + const req = { ...bidRequests[0], params: { bidfloor: 42 } } + const bidRequest = spec.buildRequests([req])[0]; + expect(bidRequest.data).to.have.property('fp'); + expect(bidRequest.data.fp).to.equal(42); + }); + + describe('product id', function () { + it('should set the correct pi param if native param is found', function () { + const request = { ...bidRequests[0], params: { ...zoneParam, native: 2 } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(5); + }); + it('should set the correct pi param for video', function () { + const request = { ...bidRequests[0], params: zoneParam, mediaTypes: vidMediaTypes }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(7); + }); + it('should set the correct pi param for invideo', function () { + const invideo = { video: { ...vidMediaTypes.video, linearity: 2 } }; + const request = { ...bidRequests[0], params: zoneParam, mediaTypes: invideo }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(6); + }); + it('should set the correct pi param if slot param is found', function () { + const request = { ...bidRequests[0], params: { ...zoneParam, slot: '123s' } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(3); + }); + it('should default the pi param to 2 if only zone or pubId param is found', function () { + const zoneRequest = { ...bidRequests[0], params: zoneParam }; + const pubIdRequest = { ...bidRequests[0], params: pubIdParam }; + const zoneBidRequest = spec.buildRequests([zoneRequest])[0]; + const pubIdBidRequest = spec.buildRequests([pubIdRequest])[0]; + expect(zoneBidRequest.data.pi).to.equal(2); + expect(pubIdBidRequest.data.pi).to.equal(2); + }); + }); + + it('should return a defined sizes field for video', function () { + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.sizes).to.equal(vidMediaTypes.video.playerSize); + }); + it('should handle multiple sizes for inslot', function () { + const mediaTypes = { banner: { sizes: [[300, 250], [300, 600]] } } + const request = { ...bidRequests[0], mediaTypes }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.bf).to.equal('300x250,300x600'); + }); + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + bidRequests[0].getFloor = _ => { + return floorTestData; + }; + it('should return the value from getFloor if present', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fp).to.equal(floorTestData.floor); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(floorTestData.floor); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(bidfloor); + }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) + }); it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -133,6 +316,25 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.include.any.keys('t'); expect(bidRequest.data).to.include.any.keys('fp'); }); + it('should set iriscat parameter if iriscat param is found and is of type string', function () { + const iriscat = 'segment'; + const request = { ...bidRequests[0] }; + request.params = { ...request.params, iriscat }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.equal(iriscat); + }); + it('should not send iriscat parameter if iriscat param is not found', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.be.undefined; + }); + it('should not send iriscat parameter if iriscat param is not of type string', function () { + const iriscat = 123; + const request = { ...bidRequests[0] }; + request.params = { ...request.params, iriscat }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.be.undefined; + }); it('should send pubId if inScreenPubID param is specified', function () { const request = Object.assign({}, bidRequests[0]); delete request.params; @@ -145,21 +347,7 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.not.include.any.keys('t'); }); it('should send pubId if videoPubID param is specified', function () { - const mediaTypes = { - video: { - playerSize: [640, 480], - context: 'instream', - minduration: 1, - maxduration: 2, - linearity: 1, - startdelay: 1, - placement: 123456, - protocols: [1, 2] - } - }; - const request = Object.assign({}, bidRequests[0]); - request.mediaTypes = mediaTypes - request.params = { 'videoPubID': 123 }; + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.include.any.keys('pubId'); expect(bidRequest.data.pubId).to.equal(request.params.videoPubID); @@ -279,6 +467,20 @@ describe('gumgumAdapter', function () { const request = spec.buildRequests(bidRequests)[0]; expect(request.data).to.not.include.any.keys('tdid'); }); + it('should send IDL envelope ID if available', function () { + const idl_env = 'abc123'; + const request = { ...bidRequests[0], userId: { idl_env } }; + const bidRequest = spec.buildRequests([request])[0]; + + expect(bidRequest.data).to.have.property('idl_env'); + expect(bidRequest.data.idl_env).to.equal(idl_env); + }); + it('should not send IDL envelope if not available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + + expect(bidRequest.data).to.not.have.property('idl_env'); + }); it('should send schain parameter in serialized form', function () { const serializedForm = '1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com' const request = spec.buildRequests(bidRequests)[0]; @@ -307,60 +509,85 @@ describe('gumgumAdapter', function () { }) describe('interpretResponse', function () { - let serverResponse = { - 'ad': { - 'id': 29593, - 'width': 300, - 'height': 250, - 'ipd': 2000, - 'markup': '

I am an ad

', - 'ii': true, - 'du': null, - 'price': 0, - 'zi': 0, - 'impurl': 'http://g2.gumgum.com/ad/view', - 'clsurl': 'http://g2.gumgum.com/ad/close' + const metaData = { adomain: ['advertiser.com'], mediaType: BANNER } + const serverResponse = { + ad: { + id: 29593, + width: 300, + height: 250, + ipd: 2000, + markup: '

I am an ad

', + ii: true, + du: null, + price: 0, + zi: 0, + impurl: 'http://g2.gumgum.com/ad/view', + clsurl: 'http://g2.gumgum.com/ad/close' }, - 'pag': { - 't': 'ggumtest', - 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', - 'css': 'html { overflow-y: auto }', - 'js': 'console.log("environment", env);' + pag: { + t: 'ggumtest', + pvid: 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + css: 'html { overflow-y: auto }', + js: 'console.log("environment", env);' }, - 'jcsi': { t: 0, rq: 8 }, - 'thms': 10000 + jcsi: { t: 0, rq: 8 }, + thms: 10000, + meta: metaData } - let bidRequest = { + const bidRequest = { id: 12345, sizes: [[300, 250], [1, 1]], url: ENDPOINT, method: 'GET', pi: 3 } - let expectedResponse = { - 'ad': '

I am an ad

', - 'cpm': 0, - 'creativeId': 29593, - 'currency': 'USD', - 'height': '250', - 'netRevenue': true, - 'requestId': 12345, - 'width': '300', - // dealId: DEAL_ID, - // referrer: REFERER, - ttl: 60 + const expectedMetaData = { advertiserDomains: ['advertiser.com'], mediaType: BANNER }; + const expectedResponse = { + ad: '

I am an ad

', + cpm: 0, + creativeId: 29593, + currency: 'USD', + height: '250', + netRevenue: true, + requestId: 12345, + width: '300', + mediaType: BANNER, + ttl: 60, + meta: expectedMetaData }; it('should get correct bid response', function () { expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); }); + it('should set a default value for advertiserDomains if adomain is not found', function () { + const meta = { ...metaData }; + delete meta.adomain; + + const response = { ...serverResponse, meta }; + const expectedMeta = { ...expectedMetaData, advertiserDomains: [] }; + const expected = { ...expectedResponse, meta: expectedMeta }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + + it('should set a default value for meta.mediaType if mediaType is not found in the response', function () { + const meta = { ...metaData }; + delete meta.mediaType; + const response = { ...serverResponse, meta }; + const expected = { ...expectedResponse }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + it('should pass correct currency if found in bid response', function () { const cur = 'EURO'; - let response = Object.assign({}, serverResponse); - let expected = Object.assign({}, expectedResponse); + const response = { ...serverResponse }; response.ad.cur = cur; + + const expected = { ...expectedResponse }; expected.currency = cur; + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); }); @@ -385,49 +612,89 @@ describe('gumgumAdapter', function () { expect(result.length).to.equal(0); }); - it('returns 1x1 when eligible product and size available', function () { - let inscreenBidRequest = { - id: 12346, - sizes: [[300, 250], [1, 1]], - url: ENDPOINT, - method: 'GET', - data: { - pi: 2, - t: 'ggumtest' + describe('bidResponse width and height', function () { + it('uses response maxw and maxh for when found in bidresponse', function () { + const maxSlotAdResponse = { ...serverResponse.ad, maxw: 300, maxh: 600 }; + const result = spec.interpretResponse({ body: { ...serverResponse, ad: maxSlotAdResponse } }, bidRequest)[0]; + expect(result.width).to.equal(maxSlotAdResponse.maxw.toString()); + expect(result.height).to.equal(maxSlotAdResponse.maxh.toString()); + }); + + it('returns 1x1 when eligible product and size are available', function () { + let bidRequest = { + id: 12346, + sizes: [[300, 250], [1, 1]], + url: ENDPOINT, + method: 'GET', + data: { + pi: 5, + t: 'ggumtest' + } } - } - let inscreenServerResponse = { - 'ad': { - 'id': 2065333, - 'height': 90, - 'ipd': 2000, - 'markup': '

I am an inscreen ad

', - 'ii': true, - 'du': null, - 'price': 1, - 'zi': 0, - 'impurl': 'http://g2.gumgum.com/ad/view', - 'clsurl': 'http://g2.gumgum.com/ad/close' - }, - 'pag': { - 't': 'ggumtest', - 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', - 'css': 'html { overflow-y: auto }', - 'js': 'console.log("environment", env);' - }, - 'thms': 10000 - } - let result = spec.interpretResponse({ body: inscreenServerResponse }, inscreenBidRequest); - expect(result[0].width).to.equal('1'); - expect(result[0].height).to.equal('1'); + let serverResponse = { + 'ad': { + 'id': 2065333, + 'height': 90, + 'ipd': 2000, + 'markup': '

Hello

', + 'ii': true, + 'du': null, + 'price': 1, + 'zi': 0, + 'impurl': 'http://g2.gumgum.com/ad/view', + 'clsurl': 'http://g2.gumgum.com/ad/close' + }, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + }, + 'thms': 10000 + } + let result = spec.interpretResponse({ body: serverResponse }, bidRequest); + expect(result[0].width).to.equal('1'); + expect(result[0].height).to.equal('1'); + }); + + it('uses response width and height for inscreen product', function () { + const result = spec.interpretResponse({ body: serverResponse }, bidRequest)[0]; + expect(result.width).to.equal(serverResponse.ad.width.toString()); + expect(result.height).to.equal(serverResponse.ad.height.toString()); + }); + + it('defaults to use bidRequest sizes', function () { + const { ad, jcsi, pag, thms, meta } = serverResponse + const noAdSizes = { ...ad } + delete noAdSizes.width + delete noAdSizes.height + const responseWithoutSizes = { jcsi, pag, thms, meta, ad: noAdSizes } + const request = { ...bidRequest, sizes: [[100, 200]] } + const result = spec.interpretResponse({ body: responseWithoutSizes }, request)[0]; + + expect(result.width).to.equal(request.sizes[0][0].toString()) + expect(result.height).to.equal(request.sizes[0][1].toString()) + }); }); it('updates jcsi object when the server response jcsi prop is found', function () { - const response = Object.assign({cw: 'AD_JSON'}, serverResponse); + const response = Object.assign({ cw: 'AD_JSON' }, serverResponse); const bidResponse = spec.interpretResponse({ body: response }, bidRequest)[0].ad; const decodedResponse = JSON.parse(atob(bidResponse)); expect(decodedResponse.jcsi).to.eql(JCSI); }); + + it('sets the correct mediaType depending on product', function () { + const bannerBidResponse = spec.interpretResponse({ body: serverResponse }, bidRequest)[0]; + const invideoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 6 } })[0]; + const videoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 7 } })[0]; + expect(bannerBidResponse.mediaType).to.equal(BANNER); + expect(invideoBidResponse.mediaType).to.equal(VIDEO); + expect(videoBidResponse.mediaType).to.equal(VIDEO); + }); + + it('sets a vastXml property if mediaType is video', function () { + const videoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 7 } })[0]; + expect(videoBidResponse.vastXml).to.exist; + }); }) describe('getUserSyncs', function () { const syncOptions = { diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js index 08a83ce981f..9861069f260 100644 --- a/test/spec/modules/h12mediaBidAdapter_spec.js +++ b/test/spec/modules/h12mediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/h12mediaBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; describe('H12 Media Adapter', function () { const DEFAULT_CURRENCY = 'USD'; @@ -21,6 +22,7 @@ describe('H12 Media Adapter', function () { auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f313', params: { pubid: 123321, + pubsubid: 'pubsubtestid', }, }; @@ -72,34 +74,34 @@ describe('H12 Media Adapter', function () { currency: 'EUR', netRevenue: true, ttl: 500, - bids: [{ + bid: { bidId: validBid.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad
', - usersync: [ - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} - ], meta: { advertiserId: '54321', advertiserName: 'My advertiser', advertiserDomains: ['test.com'] } - }] + }, + usersync: [ + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} + ], }; const serverResponse2 = { - bids: [{ + bid: { bidId: validBid2.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad 2
', - }] + } }; function removeElement(id) { @@ -152,6 +154,10 @@ describe('H12 Media Adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + sandbox.stub(frameElement, 'getBoundingClientRect').returns({ + left: 10, + top: 10, + }); }); afterEach(function () { @@ -186,36 +192,62 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); + expect(requestsData).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); }); it('should return empty bid size', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData2).to.deep.include({adunitSize: []}); + }); + + it('should return pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData).to.include({pubsubid: 'pubsubtestid'}); + expect(requestsData2).to.include({pubsubid: ''}); + }); - expect(requestsData.bidrequests[1]).to.deep.include({adunitSize: []}); + it('should return empty for incorrect pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidWithPub = {...validBid}; + bidWithPub.params.pubsubid = 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'; // More than 32 chars + const requests = spec.buildRequests([bidWithPub], bidderRequest); + const requestsData = requests[0].data.bidrequest; + + expect(requestsData).to.include({pubsubid: ''}); }); it('should return bid size from params', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; - expect(requestsData.bidrequests[1]).to.include({size: validBid2.params.size}); + expect(requestsData).to.include({size: ''}); + expect(requestsData2).to.include({size: validBid2.params.size}); }); it('should return GDPR info', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); + expect(requestsData2).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); }); it('should not have error on empty GDPR', function () { @@ -223,9 +255,23 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const bidderRequestWithoutGDRP = {...bidderRequest, gdprConsent: null}; const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutGDRP); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: false}); + expect(requestsData2).to.include({gdpr: false}); + }); + + it('should not have error on empty USP', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidderRequestWithoutUSP = {...bidderRequest, uspConsent: null}; + const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutUSP); + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; + + expect(requestsData).to.include({usp: false}); + expect(requestsData2).to.include({usp: false}); }); it('should create single POST', function () { @@ -233,7 +279,8 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - expect(requests.method).to.equal('POST'); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); }); }); @@ -241,42 +288,44 @@ describe('H12 Media Adapter', function () { it('should return coords', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.deep.include({coords: {x: 10, y: 10}}); + expect(requestsData).to.deep.include({coords: {x: 10, y: 10}}); }); - it('should define not iframe', function () { + it('should define iframe', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; - expect(requestsData).to.include({isiframe: false}); + expect(requestsData).to.include({isiframe: true}); + expect(requestsData2).to.include({isiframe: true}); }); it('should define visible element', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: false}); + expect(requestsData).to.include({ishidden: false}); }); it('should define invisible element', function () { createElementInvisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); it('should define hidden element', function () { createElementHidden(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); }); @@ -290,27 +339,27 @@ describe('H12 Media Adapter', function () { it('should return no bids if the response is empty', function () { const bidResponse = spec.interpretResponse({ body: [] }, { validBid }); - expect(bidResponse.length).to.equal(0); + expect(bidResponse).to.be.empty; }); it('should return valid bid responses', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse}, request); + const bidResponse = spec.interpretResponse({body: serverResponse}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid.bidId, - ad: serverResponse.bids[0].ad, + ad: serverResponse.bid.ad, mediaType: 'banner', - creativeId: serverResponse.bids[0].creativeId, - cpm: serverResponse.bids[0].cpm, - width: serverResponse.bids[0].width, - height: serverResponse.bids[0].height, + creativeId: serverResponse.bid.creativeId, + cpm: serverResponse.bid.cpm, + width: serverResponse.bid.width, + height: serverResponse.bid.height, currency: 'EUR', netRevenue: true, ttl: 500, - meta: serverResponse.bids[0].meta, + meta: serverResponse.bid.meta, pubid: validBid.params.pubid }); }); @@ -319,17 +368,17 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse2}, request); + const bidResponse = spec.interpretResponse({body: serverResponse2}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid2.bidId, - ad: serverResponse2.bids[0].ad, + ad: serverResponse2.bid.ad, mediaType: 'banner', - creativeId: serverResponse2.bids[0].creativeId, - cpm: serverResponse2.bids[0].cpm, - width: serverResponse2.bids[0].width, - height: serverResponse2.bids[0].height, - meta: serverResponse2.bids[0].meta, + creativeId: serverResponse2.bid.creativeId, + cpm: serverResponse2.bid.cpm, + width: serverResponse2.bid.width, + height: serverResponse2.bid.height, + meta: serverResponse2.bid.meta, pubid: validBid2.params.pubid, currency: DEFAULT_CURRENCY, netRevenue: DEFAULT_NET_REVENUE, diff --git a/test/spec/modules/haloIdSystem_spec.js b/test/spec/modules/haloIdSystem_spec.js new file mode 100644 index 00000000000..0b8fff12abe --- /dev/null +++ b/test/spec/modules/haloIdSystem_spec.js @@ -0,0 +1,57 @@ +import { haloIdSubmodule, storage } from 'modules/haloIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + +describe('HaloIdSystem', function () { + describe('getId', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + it('gets a haloId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://id.halo.ad.gt/api/v1/pbhid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); + }); + + it('gets a cached haloid', function() { + const config = { + params: {} + }; + getDataFromLocalStorageStub.withArgs('auHaloId').returns('tstCachedHaloId1'); + + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'tstCachedHaloId1'}); + }); + + it('allows configurable id url', function() { + const config = { + params: { + url: 'https://haloid.publync.com' + } + }; + const callbackSpy = sinon.spy(); + const callback = haloIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq('https://haloid.publync.com'); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); + }); + }); +}); diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js new file mode 100644 index 00000000000..32c0338b87f --- /dev/null +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -0,0 +1,762 @@ +import {config} from 'src/config.js'; +import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, haloSubmodule, storage} from 'modules/haloRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('haloRtdProvider', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('haloSubmodule', function() { + it('successfully instantiates', function () { + expect(haloSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Real-Time Data', function() { + it('merges ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + const setConfigUserObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const setConfigSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + }, + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1945' + }, + { + id: '2003' + } + ] + }; + + const rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getConfig().ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); + }); + + it('merges ortb2 data without duplication', function() { + let rtdConfig = {}; + let bidConfig = {}; + + 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' + } + ] + } + + config.setConfig({ + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }); + + const rtd = { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.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('merges bidder-specific ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const configUserObj1 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '1776' + }] + }; + + const configUserObj2 = { + name: 'www.dataprovider2.com', + ext: { segtax: 3 }, + segment: [{ + id: '1914' + }] + }; + + const configUserObj3 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '2003' + }] + }; + + const configSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + }; + + const configSiteObj2 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + } + ] + }; + + config.setBidderConfig({ + bidders: ['adbuzz'], + config: { + ortb2: { + user: { + data: [configUserObj1, configUserObj2] + }, + site: { + content: { + data: [configSiteObj1] + } + } + } + } + }); + + config.setBidderConfig({ + bidders: ['pubvisage'], + config: { + ortb2: { + user: { + data: [configUserObj3] + }, + site: { + content: { + data: [configSiteObj2] + } + } + } + } + }); + + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + segtax: 501 + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const rtdUserObj2 = { + name: 'www.dataprovider2.com', + ext: { + segtax: 502 + }, + segment: [ + { + id: '1939' + } + ] + }; + + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '441' + }, + { + id: '442' + } + ] + }; + + const rtdSiteObj2 = { + name: 'www.dataprovider6.com', + ext: { + segtax: 2 + }, + segment: [ + { + id: '676' + } + ] + }; + + const rtd = { + ortb2b: { + adbuzz: { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }, + pubvisage: { + ortb2: { + user: { + data: [rtdUserObj2] + }, + site: { + content: { + data: [rtdSiteObj2] + } + } + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([configUserObj1, configUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj1, rtdSiteObj1]); + + ortb2Config = config.getBidderConfig().pubvisage.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([configUserObj3, rtdUserObj2]); + expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); + }); + + it('merges bidder-specific ortb2 data without duplication', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const userObj1 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '1776' + }] + }; + + const userObj2 = { + name: 'www.dataprovider2.com', + ext: { segtax: 3 }, + segment: [{ + id: '1914' + }] + }; + + const userObj3 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '2003' + }] + }; + + const siteObj1 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + }; + + const siteObj2 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + } + ] + }; + + config.setBidderConfig({ + bidders: ['adbuzz'], + config: { + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + } + }); + + config.setBidderConfig({ + bidders: ['pubvisage'], + config: { + ortb2: { + user: { + data: [userObj3] + }, + site: { + content: { + data: [siteObj2] + } + } + } + } + }); + + const rtd = { + ortb2b: { + adbuzz: { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }, + pubvisage: { + ortb2: { + user: { + data: [userObj2, userObj3] + }, + site: { + content: { + data: [siteObj1, siteObj2] + } + } + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([userObj1]); + 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); + + ortb2Config = config.getBidderConfig().pubvisage.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([userObj3, userObj3]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1, siteObj2]); + + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(2); + }); + + it('allows publisher defined rtd ortb2 logic', function() { + const rtdConfig = { + params: { + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + if (rtd.ortb2.user.data[0].segment[0].id == '1776') { + pbConfig.setConfig({ortb2: rtd.ortb2}); + } else { + pbConfig.setConfig({ortb2: {}}); + } + } + } + }; + + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + let rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + }; + + config.resetConfig(); + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + + const rtdUserObj2 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [{ + id: 'pubseg1' + }] + }; + + rtd = { + ortb2: { + user: { + data: [rtdUserObj2] + } + } + }; + + config.resetConfig(); + + pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2).to.deep.equal({}); + }); + + it('allows publisher defined adunit logic', function() { + const rtdConfig = { + params: { + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz') { + for (var k = 0; k < rtd.adBuzz.length; k++) { + bid.adBuzzData.segments.adBuzz.push(rtd.adBuzz[k]); + } + } else if (bid.bidder == 'trueBid') { + for (var k = 0; k < rtd.trueBid.length; k++) { + bid.trueBidSegments.push(rtd.trueBid[k]); + } + } + } + } + } + } + }; + + let bidConfig = { + adUnits: [ + { + bids: [ + { + bidder: 'adBuzz', + adBuzzData: { + segments: { + adBuzz: [ + { + id: 'adBuzzSeg1' + } + ] + } + } + }, + { + bidder: 'trueBid', + trueBidSegments: [] + } + ] + } + ] + }; + + const rtd = { + adBuzz: [{id: 'adBuzzSeg2'}, {id: 'adBuzzSeg3'}], + trueBid: [{id: 'truebidSeg1'}, {id: 'truebidSeg2'}, {id: 'truebidSeg3'}] + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[0].id).to.equal('adBuzzSeg1'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[1].id).to.equal('adBuzzSeg2'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[2].id).to.equal('adBuzzSeg3'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[0].id).to.equal('truebidSeg1'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[1].id).to.equal('truebidSeg2'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[2].id).to.equal('truebidSeg3'); + }); + }); + + 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(RTD_LOCAL_NAME).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('gets real-time data via async request', function() { + const setConfigSiteObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + } + + config.setConfig({ + ortb2: { + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdConfig = { + params: { + segmentCache: false, + usePubHalo: true, + requestParams: { + publisherId: 'testPub1' + } + } + }; + + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + }; + + const data = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHaloId1'); + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + + let request = server.requests[0]; + let postData = JSON.parse(request.requestBody); + expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); + expect(postData.userIds).to.have.deep.property('haloId', 'testHaloId1'); + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + }); +}); diff --git a/test/spec/modules/hpmdnetworkBidAdapter_spec.js b/test/spec/modules/hpmdnetworkBidAdapter_spec.js deleted file mode 100644 index 9023fb248e9..00000000000 --- a/test/spec/modules/hpmdnetworkBidAdapter_spec.js +++ /dev/null @@ -1,148 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/hpmdnetworkBidAdapter.js'; - -describe('HPMDNetwork Adapter', function() { - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - const validBid = { - bidder: 'hpmdnetwork', - params: { - placementId: '1' - } - }; - - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - - it('should return false for when required params are not passed', function () { - const invalidBid = { - bidder: 'hpmdnetwork', - params: {} - }; - - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const bidRequests = [ - { - bidId: 'bid1', - bidder: 'hpmdnetwork', - params: { - placementId: '1' - } - }, - { - bidId: 'bid2', - bidder: 'hpmdnetwork', - params: { - placementId: '2', - } - } - ]; - const bidderRequest = { - refererInfo: { - referer: 'https://example.com?foo=bar' - } - }; - - const bidRequest = spec.buildRequests(bidRequests, bidderRequest); - - it('should build single POST request for multiple bids', function() { - expect(bidRequest.method).to.equal('POST'); - expect(bidRequest.url).to.equal('https://banner.hpmdnetwork.ru/bidder/request'); - expect(bidRequest.data).to.be.an('object'); - expect(bidRequest.data.places).to.be.an('array'); - expect(bidRequest.data.places).to.have.lengthOf(2); - }); - - it('should pass bid parameters', function() { - const place1 = bidRequest.data.places[0]; - const place2 = bidRequest.data.places[1]; - - expect(place1.placementId).to.equal('1'); - expect(place2.placementId).to.equal('2'); - expect(place1.id).to.equal('bid1'); - expect(place2.id).to.equal('bid2'); - }); - - it('should pass site parameters', function() { - const url = bidRequest.data.url; - - expect(url).to.be.an('String'); - expect(url).to.equal('https://example.com?foo=bar'); - }); - - it('should pass settings', function() { - const settings = bidRequest.data.settings; - - expect(settings).to.be.an('object'); - expect(settings.currency).to.equal('RUB'); - }); - }); - - describe('interpretResponse', function () { - const serverResponse = { - body: { - 'bids': - [ - { - 'cpm': 20, - 'currency': 'RUB', - 'displayUrl': 'https://banner.hpmdnetwork.ru/bidder/display?dbid=0&vbid=168', - 'id': '1', - 'creativeId': '11111', - }, - { - 'cpm': 30, - 'currency': 'RUB', - 'displayUrl': 'https://banner.hpmdnetwork.ru/bidder/display?dbid=0&vbid=170', - 'id': '2', - 'creativeId': '22222', - 'width': 300, - 'height': 250, - }, - ] - } - }; - - const bids = spec.interpretResponse(serverResponse); - - it('should return empty array for response with no bids', function() { - const emptyBids = spec.interpretResponse({ body: {} }); - - expect(emptyBids).to.have.lengthOf(0); - }); - - it('should parse all bids from response', function() { - expect(bids).to.have.lengthOf(2); - }); - - it('should parse bid without sizes', function() { - expect(bids[0].requestId).to.equal('1'); - expect(bids[0].cpm).to.equal(20); - expect(bids[0].width).to.equal(1); - expect(bids[0].height).to.equal(1); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].currency).to.equal('RUB'); - expect(bids[0]).to.have.property('creativeId'); - expect(bids[0].creativeId).to.equal('11111'); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].ad).to.include(''); - }); - - it('should parse bid with sizes', function() { - expect(bids[1].requestId).to.equal('2'); - expect(bids[1].cpm).to.equal(30); - expect(bids[1].width).to.equal(300); - expect(bids[1].height).to.equal(250); - expect(bids[1].ttl).to.equal(300); - expect(bids[1].currency).to.equal('RUB'); - expect(bids[1]).to.have.property('creativeId'); - expect(bids[1].creativeId).to.equal('22222'); - expect(bids[1].netRevenue).to.equal(true); - expect(bids[1].ad).to.include(''); - }); - }); -}); diff --git a/test/spec/modules/hybridBidAdapter_spec.js b/test/spec/modules/hybridBidAdapter_spec.js index e5ad878c9b1..ffbc27293fb 100644 --- a/test/spec/modules/hybridBidAdapter_spec.js +++ b/test/spec/modules/hybridBidAdapter_spec.js @@ -25,16 +25,22 @@ describe('Hybrid.ai Adapter', function() { placeId: PLACE_ID, placement: 'video' } + const inImageMandatoryParams = { + placeId: PLACE_ID, + placement: 'inImage', + imageUrl: 'https://hybrid.ai/images/image.jpg' + } const validBidRequests = [ getSlotConfigs({ banner: {} }, bannerMandatoryParams), - getSlotConfigs({ video: {playerSize: [[640, 480]]} }, videoMandatoryParams) + getSlotConfigs({ video: {playerSize: [[640, 480]], context: 'outstream'} }, videoMandatoryParams), + getSlotConfigs({ banner: {sizes: [0, 0]} }, inImageMandatoryParams) ] describe('isBidRequestValid method', function() { describe('returns true', function() { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function() { + describe('and banner placement has the correct value', function() { const slotConfig = getSlotConfigs( - { banner: {} }, + {banner: {}}, { placeId: PLACE_ID, placement: 'banner' @@ -43,6 +49,22 @@ describe('Hybrid.ai Adapter', function() { const isBidRequestValid = spec.isBidRequestValid(slotConfig) expect(isBidRequestValid).to.equal(true) }) + describe('and In-Image placement has the correct value', function() { + const slotConfig = getSlotConfigs( + { + banner: { + sizes: [[0, 0]] + } + }, + { + placeId: PLACE_ID, + placement: 'inImage', + imageUrl: 'imageUrl' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) describe('when video slot has all mandatory params.', function() { it('should return true, when video mediatype object are correct.', function() { const slotConfig = getSlotConfigs( @@ -84,6 +106,15 @@ describe('Hybrid.ai Adapter', function() { ) expect(isBidRequestValid).to.equal(false) }) + it('does not have the imageUrl.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) it('does not have a the correct placement.', function() { const isBidRequestValid = spec.isBidRequestValid( createSlotconfig({ @@ -224,7 +255,8 @@ describe('Hybrid.ai Adapter', function() { currency: 'USD', content: 'html', width: 100, - height: 100 + height: 100, + advertiserDomains: ['hybrid.ai'] } ] } @@ -238,6 +270,37 @@ describe('Hybrid.ai Adapter', function() { expect(bids[0].height).to.equal(100) expect(bids[0].currency).to.equal('USD') expect(bids[0].netRevenue).to.equal(true) + expect(bids[0].meta.advertiserDomains).to.deep.equal(['hybrid.ai']) + expect(typeof bids[0].ad).to.equal('string') + }) + it('should return a In-Image bid', function() { + const request = spec.buildRequests([validBidRequests[2]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + inImage: { + actionUrls: {} + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) expect(typeof bids[0].ad).to.equal('string') }) }) @@ -253,7 +316,8 @@ describe('Hybrid.ai Adapter', function() { currency: 'USD', content: 'html', width: 100, - height: 100 + height: 100, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' } ] } diff --git a/test/spec/modules/iasBidAdapter_spec.js b/test/spec/modules/iasBidAdapter_spec.js deleted file mode 100644 index 1743ac947e6..00000000000 --- a/test/spec/modules/iasBidAdapter_spec.js +++ /dev/null @@ -1,343 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/iasBidAdapter.js'; - -describe('iasBidAdapter is an adapter that', function () { - it('has the correct bidder code', function () { - expect(spec.code).to.equal('ias'); - }); - describe('has a method `isBidRequestValid` that', function () { - it('exists', function () { - expect(spec.isBidRequestValid).to.be.a('function'); - }); - it('returns false if bid params misses `pubId`', function () { - expect(spec.isBidRequestValid( - { - params: { - adUnitPath: 'someAdUnitPath' - } - })).to.equal(false); - }); - it('returns false if bid params misses `adUnitPath`', function () { - expect(spec.isBidRequestValid( - { - params: { - pubId: 'somePubId' - } - })).to.equal(false); - }); - it('returns true otherwise', function () { - expect(spec.isBidRequestValid( - { - params: { - adUnitPath: 'someAdUnitPath', - pubId: 'somePubId', - someOtherParam: 'abc' - } - })).to.equal(true); - }); - }); - - describe('has a method `buildRequests` that', function () { - it('exists', function () { - expect(spec.buildRequests).to.be.a('function'); - }); - describe('given bid requests, returns a `ServerRequest` instance that', function () { - let bidRequests, IAS_HOST; - beforeEach(function () { - IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; - bidRequests = [ - { - adUnitCode: 'one-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/a/b/c' - }, - sizes: [ - [10, 20], - [300, 400] - ], - transactionId: 'someTransactionId' - }, - { - adUnitCode: 'two-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/d/e/f' - }, - sizes: [ - [50, 60] - ], - transactionId: 'someTransactionId' - } - ]; - }); - it('has property `method` of `GET`', function () { - expect(spec.buildRequests(bidRequests)).to.deep.include({ - method: 'GET' - }); - }); - it('has property `url` to be the correct IAS endpoint', function () { - expect(spec.buildRequests(bidRequests)).to.deep.include({ - url: IAS_HOST - }); - }); - it('only includes the first `bidRequest` as the bidRequest variable on a multiple slot request', function () { - expect(spec.buildRequests(bidRequests).bidRequest.adUnitCode).to.equal(bidRequests[0].adUnitCode); - }); - describe('has property `data` that is an encode query string containing information such as', function () { - let val; - const ANID_PARAM = 'anId'; - const SLOT_PARAM = 'slot'; - const SLOT_ID_PARAM = 'id'; - const SLOT_SIZE_PARAM = 'ss'; - const SLOT_AD_UNIT_PATH_PARAM = 'p'; - - beforeEach(function () { - val = decodeURI(spec.buildRequests(bidRequests).data); - }); - it('publisher id', function () { - expect(val).to.have.string(`${ANID_PARAM}=1234`); - }); - it('ad slot`s id, size and ad unit path', function () { - expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:one-div-id,${SLOT_SIZE_PARAM}:[10.20,300.400],${SLOT_AD_UNIT_PATH_PARAM}:/a/b/c}`); - expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:two-div-id,${SLOT_SIZE_PARAM}:[50.60],${SLOT_AD_UNIT_PATH_PARAM}:/d/e/f}`); - }); - it('window size', function () { - expect(val).to.match(/.*wr=[0-9]*\.[0-9]*/); - }); - it('screen size', function () { - expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/); - }); - it('url value', function () { - expect(val).to.match(/.*url=https?%3A%2F%2F[^\s$.?#].[^\s]*/); - }); - }); - it('has property `bidRequest` that is the first passed in bid request', function () { - expect(spec.buildRequests(bidRequests)).to.deep.include({ - bidRequest: bidRequests[0] - }); - }); - }); - }); - describe('has a method `interpretResponse` that', function () { - it('exists', function () { - expect(spec.interpretResponse).to.be.a('function'); - }); - describe('returns a list of bid response that', function () { - let bidRequests, bidResponse, slots, serverResponse; - beforeEach(function () { - bidRequests = [ - { - adUnitCode: 'one-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId1', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/a/b/c' - }, - sizes: [ - [10, 20], - [300, 400] - ], - transactionId: 'someTransactionId' - }, - { - adUnitCode: 'two-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId2', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/d/e/f' - }, - sizes: [ - [50, 60] - ], - transactionId: 'someTransactionId' - } - ]; - const request = { - bidRequest: { - bidId: '102938' - } - }; - slots = {}; - slots['test-div-id'] = { - id: '1234', - vw: ['60', '70'] - }; - slots['test-div-id-two'] = { - id: '5678', - vw: ['80', '90'] - }; - serverResponse = { - body: { - brandSafety: { - adt: 'adtVal', - alc: 'alcVal', - dlm: 'dlmVal', - drg: 'drgVal', - hat: 'hatVal', - off: 'offVal', - vio: 'vioVal' - }, - fr: 'false', - slots: slots - }, - headers: {} - }; - bidResponse = spec.interpretResponse(serverResponse, request); - }); - it('has IAS keyword `adt` as property', function () { - expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' }); - }); - it('has IAS keyword `alc` as property', function () { - expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' }); - }); - it('has IAS keyword `dlm` as property', function () { - expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' }); - }); - it('has IAS keyword `drg` as property', function () { - expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' }); - }); - it('has IAS keyword `hat` as property', function () { - expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' }); - }); - it('has IAS keyword `off` as property', function () { - expect(bidResponse[0]).to.deep.include({ off: 'offVal' }); - }); - it('has IAS keyword `vio` as property', function () { - expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' }); - }); - it('has IAS keyword `fr` as property', function () { - expect(bidResponse[0]).to.deep.include({ fr: 'false' }); - }); - it('has property `slots`', function () { - expect(bidResponse[0]).to.deep.include({ slots: slots }); - }); - it('response is the same for multiple slots', function () { - var adapter = spec; - var requests = adapter.buildRequests(bidRequests); - expect(adapter.interpretResponse(serverResponse, requests)).to.length(2); - }); - }); - describe('returns a list of bid response that with custom value', function () { - let bidRequests, bidResponse, slots, custom, serverResponse; - beforeEach(function () { - bidRequests = [ - { - adUnitCode: 'one-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId1', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/a/b/c' - }, - sizes: [ - [10, 20], - [300, 400] - ], - transactionId: 'someTransactionId' - }, - { - adUnitCode: 'two-div-id', - auctionId: 'someAuctionId', - bidId: 'someBidId2', - bidder: 'ias', - bidderRequestId: 'someBidderRequestId', - params: { - pubId: '1234', - adUnitPath: '/d/e/f' - }, - sizes: [ - [50, 60] - ], - transactionId: 'someTransactionId' - } - ]; - const request = { - bidRequest: { - bidId: '102938' - } - }; - slots = {}; - slots['test-div-id'] = { - id: '1234', - vw: ['60', '70'] - }; - slots['test-div-id-two'] = { - id: '5678', - vw: ['80', '90'] - }; - custom = {}; - custom['ias-kw'] = ['IAS_1_KW', 'IAS_2_KW']; - serverResponse = { - body: { - brandSafety: { - adt: 'adtVal', - alc: 'alcVal', - dlm: 'dlmVal', - drg: 'drgVal', - hat: 'hatVal', - off: 'offVal', - vio: 'vioVal' - }, - fr: 'false', - slots: slots, - custom: custom - }, - headers: {} - }; - bidResponse = spec.interpretResponse(serverResponse, request); - }); - it('has IAS keyword `adt` as property', function () { - expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' }); - }); - it('has IAS keyword `alc` as property', function () { - expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' }); - }); - it('has IAS keyword `dlm` as property', function () { - expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' }); - }); - it('has IAS keyword `drg` as property', function () { - expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' }); - }); - it('has IAS keyword `hat` as property', function () { - expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' }); - }); - it('has IAS keyword `off` as property', function () { - expect(bidResponse[0]).to.deep.include({ off: 'offVal' }); - }); - it('has IAS keyword `vio` as property', function () { - expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' }); - }); - it('has IAS keyword `fr` as property', function () { - expect(bidResponse[0]).to.deep.include({ fr: 'false' }); - }); - it('has property `slots`', function () { - expect(bidResponse[0]).to.deep.include({ slots: slots }); - }); - it('has property `custom`', function () { - expect(bidResponse[0]).to.deep.include({ custom: custom }); - }); - it('response is the same for multiple slots', function () { - var adapter = spec; - var requests = adapter.buildRequests(bidRequests); - expect(adapter.interpretResponse(serverResponse, requests)).to.length(3); - }); - }); - }); -}); diff --git a/test/spec/modules/iasRtdProvider_spec.js b/test/spec/modules/iasRtdProvider_spec.js new file mode 100644 index 00000000000..e5e12355566 --- /dev/null +++ b/test/spec/modules/iasRtdProvider_spec.js @@ -0,0 +1,89 @@ +import { iasSubModule } from 'modules/iasRtdProvider.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; + +describe('iasRtdProvider is a RTD provider that', function () { + it('has the correct module name', function () { + expect(iasSubModule.name).to.equal('ias'); + }); + describe('has a method `init` that', function () { + it('exists', function () { + expect(iasSubModule.init).to.be.a('function'); + }); + it('returns true', function () { + expect(iasSubModule.init()).to.equal(true); + }); + }); + describe('has a method `getBidRequestData` that', function () { + it('exists', function () { + expect(iasSubModule.getBidRequestData).to.be.a('function'); + }); + it('verify config params', function () { + expect(config.name).to.not.be.undefined; + expect(config.name).to.equal('ias'); + expect(config.params.pubId).to.not.be.undefined; + expect(config.params).to.have.property('pubId'); + }); + it('invoke method', function () { + const callback = sinon.spy(); + let request; + const adUnitsOriginal = adUnits; + iasSubModule.getBidRequestData({ adUnits: adUnits }, callback, config); + request = server.requests[0]; + server.respond(); + expect(request.url).to.be.include(`https://pixel.adsafeprotected.com/services/pub?anId=1234`); + expect(adUnits).to.length(2); + expect(adUnits[0]).to.be.eq(adUnitsOriginal[0]); + }); + }); + + describe('has a method `getTargetingData` that', function () { + it('exists', function () { + expect(iasSubModule.getTargetingData).to.be.a('function'); + }); + it('invoke method', function () { + const targeting = iasSubModule.getTargetingData(adUnits, config); + expect(adUnits).to.length(2); + expect(targeting).to.be.not.null; + expect(targeting).to.be.not.empty; + }); + }); +}); + +const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: 1234 + } +}; + +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, + } + }] + }]; diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js new file mode 100644 index 00000000000..be5998967c9 --- /dev/null +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -0,0 +1,462 @@ +import adapterManager from '../../../src/adapterManager.js'; +import id5AnalyticsAdapter from '../../../modules/id5AnalyticsAdapter.js'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import events from '../../../src/events.js'; +import constants from '../../../src/constants.json'; +import { generateUUID } from '../../../src/utils.js'; + +const CONFIG_URL = 'https://api.id5-sync.com/analytics/12349/pbjs'; +const INGEST_URL = 'https://test.me/ingest'; + +describe('ID5 analytics adapter', () => { + let server; + let config; + + beforeEach(() => { + server = sinon.createFakeServer(); + config = { + options: { + partnerId: 12349, + } + }; + }); + + afterEach(() => { + server.restore(); + }); + + it('registers itself with the adapter manager', () => { + const adapter = adapterManager.getAnalyticsAdapter('id5Analytics'); + expect(adapter).to.exist; + expect(adapter.gvlid).to.be.a('number'); + expect(adapter.adapter).to.equal(id5AnalyticsAdapter); + }); + + it('tolerates undefined or empty config', () => { + id5AnalyticsAdapter.enableAnalytics(undefined); + id5AnalyticsAdapter.enableAnalytics({}); + }); + + it('calls configuration endpoint', () => { + server.respondWith('GET', CONFIG_URL, [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + `{ "sampling": 0, "ingestUrl": "${INGEST_URL}" }` + ]); + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + + expect(server.requests).to.have.length(1); + + id5AnalyticsAdapter.disableAnalytics(); + }); + + it('does not call configuration endpoint when partner id is missing', () => { + id5AnalyticsAdapter.enableAnalytics({}); + server.respond(); + + expect(server.requests).to.have.length(0); + + id5AnalyticsAdapter.disableAnalytics(); + }); + + describe('after configuration', () => { + let auction; + + beforeEach(() => { + server.respondWith('GET', CONFIG_URL, [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + `{ "sampling": 1, "ingestUrl": "${INGEST_URL}" }` + ]); + + server.respondWith('POST', INGEST_URL, [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + '' + ]); + + auction = { + auctionId: generateUUID(), + adUnits: [{ + 'code': 'user-728', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90]] + } + }, + adUnitCodes: ['user-728'] + }], + }; + }); + + afterEach(() => { + id5AnalyticsAdapter.disableAnalytics(); + }); + + it('sends auction end events to the backend', () => { + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + events.emit(constants.EVENTS.AUCTION_END, auction); + server.respond(); + + // Why 3? 1: config, 2: tcfEnforcement, 3: auctionEnd + // tcfEnforcement? yes, gdprEnforcement module emits in reaction to auctionEnd + expect(server.requests).to.have.length(3); + + const body1 = JSON.parse(server.requests[1].requestBody); + expect(body1.source).to.equal('pbjs'); + expect(body1.event).to.equal('tcf2Enforcement'); + expect(body1.partnerId).to.equal(12349); + expect(body1.meta).to.be.a('object'); + expect(body1.meta.pbjs).to.equal($$PREBID_GLOBAL$$.version); + expect(body1.meta.sampling).to.equal(1); + expect(body1.meta.tz).to.be.a('number'); + + const body2 = JSON.parse(server.requests[2].requestBody); + expect(body2.source).to.equal('pbjs'); + expect(body2.event).to.equal('auctionEnd'); + expect(body2.partnerId).to.equal(12349); + expect(body2.meta).to.be.a('object'); + expect(body2.meta.pbjs).to.equal($$PREBID_GLOBAL$$.version); + expect(body2.meta.sampling).to.equal(1); + expect(body2.meta.tz).to.be.a('number'); + expect(body2.payload).to.eql(auction); + }); + + it('filters unwanted IDs from the events it sends', () => { + auction.adUnits[0].bids = [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': '16618951' + }, + 'userId': { + 'criteoId': '_h_y_19IMUhMZG1TOTRReHFNc29TekJ3TzQ3elhnRU81ayUyQjhiRkdJJTJGaTFXJTJCdDRnVmN4S0FETUhQbXdmQWg0M3g1NWtGbGolMkZXalclMkJvWjJDOXFDSk1HU3ZKaVElM0QlM0Q', + 'id5id': { + 'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu', + 'ext': { 'linkType': 1 } + }, + 'tdid': '888a6042-8f99-483b-aa26-23c44bc9166b' + }, + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': '_h_y_19IMUhMZG1TOTRReHFNc29TekJ3TzQ3elhnRU81ayUyQjhiRkdJJTJGaTFXJTJCdDRnVmN4S0FETUhQbXdmQWg0M3g1NWtGbGolMkZXalclMkJvWjJDOXFDSk1HU3ZKaVElM0QlM0Q', + 'atype': 1 + }] + }, { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu', + 'atype': 1, + 'ext': { 'linkType': 1 } + }] + }] + }]; + + auction.bidderRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': 'e8d15df4-d89c-44c9-8b36-812f75cbf227', + 'bidderRequestId': '1451a3c759c60359', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '16824712' + }, + 'userId': { + 'id5id': { + 'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*CmuuahP8jbPJGRCUDdT2VZ8wz0eJM8O8mNlKktlEjuYAABFEjc2c9faqDencf2hR', + 'ext': { + 'linkType': 1 + } + }, + 'sharedid': { + 'id': '01F6J4T72MRFYVWTN65WFA0H7N', + 'third': '01F6J4T72MRFYVWTN65WFA0H7N' + }, + 'tdid': '0e45f56b-ad09-4c91-b090-8bd03e0d0754' + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*CmuuahP8jbPJGRCUDdT2VZ8wz0eJM8O8mNlKktlEjuYAABFEjc2c9faqDencf2hR', + 'atype': 1, + 'ext': { + 'linkType': 1 + } + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01F6J4T72MRFYVWTN65WFA0H7N', + 'atype': 1, + 'ext': { + 'third': '01F6J4T72MRFYVWTN65WFA0H7N' + } + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': '0e45f56b-ad09-4c91-b090-8bd03e0d0754', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/6783/Kiwi/portail' + }, + 'pbadslot': '/6783/Kiwi/portail' + } + } + }, + 'adUnitCode': 'btf_leaderboard', + 'transactionId': '3ce8216e-7898-4a22-86ba-01519b62bfce', + 'sizes': [ + [ + 728, + 90 + ] + ], + 'bidId': '146661c05209a56e', + 'bidderRequestId': '1451a3c759c60359', + 'auctionId': 'e8d15df4-d89c-44c9-8b36-812f75cbf227', + 'src': 'client', + 'bidRequestsCount': 2, + 'bidderRequestsCount': 2, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1621959214757, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://www.blog.com/?pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://www.blog.com/?pbjs_debug=true' + ], + 'canonicalUrl': null + }, + 'gdprConsent': { + 'consentString': 'CPGw1WAPGw1WAAHABBENBbCsAP_AAH_AAAAAH3tf_X__b3_j-_59__t0eY1f9_7_v-0zjhfdt-8N2f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrTPsbk2Mr7NKJ7PEmnMbe2dYGH9_n93TuZKY7__8___z__-v_v____f_r-3_3__59X---_e_V399zLv9__3__9gfaASYal8AF2JY4Mk0aVQogQhWEh0AoAKKAYWiawgZXBTsrgI9QQMAEJqAjAiBBiCjFgEAAgEASERASAHggEQBEAgABACpAQgAI2AQWAFgYBAAKAaFiBFAEIEhBkcFRymBARItFBPZWAJRd7GmEIZRYAUCj-iowEShBAsDISFg4AAA.f_gAD_gAAAAA', + 'vendorData': { + 'cmpId': 7, + 'cmpVersion': 1, + 'gdprApplies': true, + 'tcfPolicyVersion': 2, + 'eventStatus': 'useractioncomplete', + 'cmpStatus': 'loaded', + 'listenerId': 47, + 'tcString': 'CPGw1WAPGw1WAAHABBENBbCsAP_AAH_AAAAAH3tf_X__b3_j-_59__t0eY1f9_7_v-0zjhfdt-8N2f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrTPsbk2Mr7NKJ7PEmnMbe2dYGH9_n93TuZKY7__8___z__-v_v____f_r-3_3__59X---_e_V399zLv9__3__9gfaASYal8AF2JY4Mk0aVQogQhWEh0AoAKKAYWiawgZXBTsrgI9QQMAEJqAjAiBBiCjFgEAAgEASERASAHggEQBEAgABACpAQgAI2AQWAFgYBAAKAaFiBFAEIEhBkcFRymBARItFBPZWAJRd7GmEIZRYAUCj-iowEShBAsDISFg4AAA.f_gAD_gAAAAA', + }, + 'gdprApplies': true, + 'addtlConsent': '1~7.12.35.62.66.70.89.93.108.122.144.149.153.162.167.184.196.221.241.253.259.272.311.317.323.326.338.348.350.415.440.448.449.482.486.491.494.495.540.571.574.585.587.588.590.725.733.780.817.839.864.867.932.938.981.986.1031.1033.1051.1092.1097.1126.1127.1170.1171.1186.1201.1204.1205.1211.1215.1230.1232.1236.1248.1276.1290.1301.1313.1344.1364.1365.1415.1419.1428.1449.1451.1509.1558.1564.1570.1577.1591.1651.1669.1712.1716.1720.1721.1725.1733.1753.1765.1799.1810.1834.1842.1870.1878.1889.1896.1911.1922.1929.2012.2072.2078.2079.2109.2177.2202.2253.2290.2299.2316.2357.2373.2526.2531.2571.2572.2575.2628.2663.2677.2776.2778.2779.2985.3033.3052.3154', + 'apiVersion': 2 + }, + 'start': 1621959214763 + }]; + + auction.bidsReceived = [{ + 'bidderCode': 'appnexus', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '99e7838aa7f1c4f', + 'requestId': '21e0b32208ee9a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.020601, + 'creativeId': 209272535, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'user-728', + 'appnexus': { + 'buyerMemberId': 11563 + }, + 'meta': { + 'advertiserId': 4388779 + }, + 'ad': 'stuff i am not interested in', + 'originalCpm': 0.020601, + 'originalCurrency': 'USD', + 'auctionId': 'c7694dbb-a583-4a73-a933-b16f1f821ba4', + // Make sure cleanup is resilient + 'someNullObject': null, + 'someUndefinedProperty': undefined + }]; + + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + events.emit(constants.EVENTS.AUCTION_END, auction); + server.respond(); + + expect(server.requests).to.have.length(3); + + const body = JSON.parse(server.requests[2].requestBody); + expect(body.event).to.equal('auctionEnd'); + expect(body.payload.adUnits[0].bids[0].userId).to.eql({ + 'criteoId': '__ID5_REDACTED__', + 'id5id': { + 'uid': '__ID5_REDACTED__', + 'ext': { + 'linkType': 1 + } + }, + 'tdid': '__ID5_REDACTED__' + }); + expect(body.payload.bidderRequests[0].bids[0].userId).to.eql({ + 'sharedid': '__ID5_REDACTED__', + 'id5id': { + 'uid': '__ID5_REDACTED__', + 'ext': { + 'linkType': 1 + } + }, + 'tdid': '__ID5_REDACTED__' + }); + body.payload.adUnits[0].bids[0].userIdAsEids.forEach((userId) => { + expect(userId.uids[0].id).to.equal('__ID5_REDACTED__'); + if (userId.uids[0].ext) { + expect(userId.uids[0].ext).to.equal('__ID5_REDACTED__'); + } + }); + body.payload.bidderRequests[0].bids[0].userIdAsEids.forEach((userId) => { + expect(userId.uids[0].id).to.equal('__ID5_REDACTED__'); + if (userId.uids[0].ext) { + expect(userId.uids[0].ext).to.equal('__ID5_REDACTED__'); + } + }); + expect(body.payload.bidsReceived[0].ad).to.equal(undefined); + expect(body.payload.bidsReceived[0].requestId).to.equal('21e0b32208ee9a'); + }); + + it('can override events to collect if configured to do so', () => { + server.respondWith('GET', CONFIG_URL, [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + `{ "sampling": 1, "ingestUrl": "${INGEST_URL}", "eventsToTrack": ["tcf2Enforcement"] }` + ]); + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + events.emit(constants.EVENTS.AUCTION_END, auction); + server.respond(); + + expect(server.requests).to.have.length(2); + const body1 = JSON.parse(server.requests[1].requestBody); + expect(body1.event).to.equal('tcf2Enforcement'); + }); + + it('can extend cleanup rules from server side', () => { + auction.bidsReceived = [{ + 'bidderCode': 'appnexus', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '99e7838aa7f1c4f', + 'requestId': '21e0b32208ee9a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.020601, + 'creativeId': 209272535, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'user-728', + 'appnexus': { + 'buyerMemberId': 11563 + }, + 'meta': { + 'advertiserId': 4388779 + }, + 'ad': 'stuff i am not interested in', + 'originalCpm': 0.020601, + 'originalCurrency': 'USD', + 'auctionId': 'c7694dbb-a583-4a73-a933-b16f1f821ba4' + }, { + 'bidderCode': 'ix', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '228f725de4a9ff09', + 'requestId': '225a42b4a8ec7287', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.06, + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': '8838044', + 'ad': 'lots of HTML code', + 'ttl': 300, + 'meta': { + 'networkId': 85, + 'brandId': 822, + 'brandName': 'Microsoft Brands', + 'advertiserDomains': [ + 'microsoftstore.com' + ] + }, + 'originalCpm': 0.06, + 'originalCurrency': 'USD', + 'auctionId': 'fe28ce44-61bb-4ed8-be3c-3e801dfddcb9', + 'responseTimestamp': 1621954632648, + 'requestTimestamp': 1621954632498, + 'bidder': 'ix', + 'adUnitCode': 'sticky_footer', + 'timeToRespond': 150, + 'pbLg': '0.00', + 'pbCg': '0.06', + 'size': '728x90', + 'adserverTargeting': { + 'hb_bidder': 'ix', + } + }]; + server.respondWith('GET', CONFIG_URL, [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + `{ "sampling": 1, "ingestUrl": "${INGEST_URL}", "additionalCleanupRules": {"auctionEnd": [{"match":["bidsReceived", "*", "requestId"],"apply":"erase"}]} }` + ]); + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + events.emit(constants.EVENTS.AUCTION_END, auction); + server.respond(); + + expect(server.requests).to.have.length(3); + const body = JSON.parse(server.requests[2].requestBody); + expect(body.event).to.equal('auctionEnd'); + expect(body.payload.bidsReceived[0].requestId).to.equal(undefined); + expect(body.payload.bidsReceived[1].requestId).to.equal(undefined); + expect(body.payload.bidsReceived[0].bidderCode).to.equal('appnexus'); + expect(body.payload.bidsReceived[1].bidderCode).to.equal('ix'); + }); + }); +}); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 05ecec8dc36..debde20e4c0 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,9 +1,21 @@ +import { + id5IdSubmodule, + ID5_STORAGE_NAME, + ID5_PRIVACY_STORAGE_NAME, + getFromLocalStorage, + storeInLocalStorage, + expDaysStr, + nbCacheName, + getNbFromCache, + storeNbInCache, + isInControlGroup +} from 'modules/id5IdSystem.js'; import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; import { config } from 'src/config.js'; -import { id5IdSubmodule } from 'modules/id5IdSystem.js'; import { server } from 'test/mocks/xhr.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; +import * as utils from 'src/utils.js'; let expect = require('chai').expect; @@ -11,33 +23,31 @@ describe('ID5 ID System', function() { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); const ID5_SOURCE = 'id5-sync.com'; - const ID5_PARTNER = 173; - const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_PARTNER}.json`; - const ID5_COOKIE_NAME = 'id5idcookie'; - const ID5_NB_COOKIE_NAME = `id5id.1st_${ID5_PARTNER}_nb`; - const ID5_EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + const ID5_TEST_PARTNER_ID = 173; + const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; + const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; + const ID5_STORED_LINK_TYPE = 1; const ID5_STORED_OBJ = { 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE + 'signature': ID5_STORED_SIGNATURE, + 'link_type': ID5_STORED_LINK_TYPE }; - const ID5_LEGACY_STORED_OBJ = { - 'ID5ID': ID5_STORED_ID - } const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; + const ID5_RESPONSE_LINK_TYPE = 2; const ID5_JSON_RESPONSE = { 'universal_uid': ID5_RESPONSE_ID, 'signature': ID5_RESPONSE_SIGNATURE, - 'link_type': 0 + 'link_type': ID5_RESPONSE_LINK_TYPE }; - function getId5FetchConfig(storageName = ID5_COOKIE_NAME, storageType = 'cookie') { + function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, params: { - partner: ID5_PARTNER + partner: ID5_TEST_PARTNER_ID }, storage: { name: storageName, @@ -50,7 +60,9 @@ describe('ID5 ID System', function() { return { name: ID5_MODULE_NAME, value: { - id5id: value + id5id: { + uid: value + } } } } @@ -62,11 +74,8 @@ describe('ID5 ID System', function() { } } } - function getFetchCookieConfig() { - return getUserSyncConfig([getId5FetchConfig()]); - } function getFetchLocalStorageConfig() { - return getUserSyncConfig([getId5FetchConfig(ID5_COOKIE_NAME, 'html5')]); + return getUserSyncConfig([getId5FetchConfig(ID5_STORAGE_NAME, 'html5')]); } function getValueConfig(value) { return getUserSyncConfig([getId5ValueConfig(value)]); @@ -80,6 +89,37 @@ describe('ID5 ID System', function() { }; } + describe('Check for valid publisher config', function() { + it('should fail with invalid config', function() { + // no config + expect(id5IdSubmodule.getId()).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ })).to.be.eq(undefined); + + // valid params, invalid storage + expect(id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); + + // valid storage, invalid params + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); + }); + + it('should warn with non-recommended storage params', function() { + let logWarnStub = sinon.stub(utils, 'logWarn'); + + id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); + expect(logWarnStub.calledOnce).to.be.true; + logWarnStub.restore(); + + id5IdSubmodule.getId({ storage: { name: ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); + expect(logWarnStub.calledOnce).to.be.true; + logWarnStub.restore(); + }); + }); + describe('Xhr Requests from getId()', function() { const responseHeader = { 'Content-Type': 'application/json' }; let callbackSpy = sinon.spy(); @@ -91,121 +131,178 @@ describe('ID5 ID System', function() { }); - it('should fail if no partner is provided in the config', function() { - expect(id5IdSubmodule.getId()).to.be.eq(undefined); - }); - - it('should call the ID5 server with 1puid field for legacy storedObj format', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_LEGACY_STORED_OBJ).callback; + it('should call the ID5 server and handle a valid response', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); expect(request.url).to.contain(ID5_ENDPOINT); expect(request.withCredentials).to.be.true; - expect(requestBody.s).to.eq(''); - expect(requestBody.partner).to.eq(ID5_PARTNER); - expect(requestBody['1puid']).to.eq(ID5_STORED_ID); + expect(requestBody.partner).to.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).to.eq('pbjs'); + expect(requestBody.pd).to.be.undefined; + expect(requestBody.s).to.be.undefined; + expect(requestBody.provider).to.be.undefined + expect(requestBody.v).to.eq('$prebid.version$'); + expect(requestBody.gdpr).to.exist; + expect(requestBody.gdpr_consent).to.be.undefined; + expect(requestBody.us_privacy).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with signature field for new storedObj format', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + it('should call the ID5 server with no signature field when no stored object', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.s).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server with signature field from stored object', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); - expect(requestBody.partner).to.eq(ID5_PARTNER); - expect(requestBody['1puid']).to.eq(''); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); it('should call the ID5 server with pd field when pd config is set', function () { const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; - let config = getId5FetchConfig().params; - config.pd = pubData; + let id5Config = getId5FetchConfig(); + id5Config.params.pd = pubData; - let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; - expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); expect(requestBody.pd).to.eq(pubData); - expect(requestBody['1puid']).to.eq(''); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with empty pd field when pd config is not set', function () { - let config = getId5FetchConfig().params; - config.pd = undefined; + it('should call the ID5 server with no pd field when pd config is not set', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.pd = undefined; - let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; - expect(requestBody.pd).to.eq(''); + expect(requestBody.pd).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with nb=1 when no stored value exists', function () { - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + it('should call the ID5 server with nb=1 when no stored value exists and reset after', function () { + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.nbPage).to.eq(1); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); - it('should call the ID5 server with incremented nb when stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + it('should call the ID5 server with incremented nb when stored value exists and reset after', function () { + storeNbInCache(ID5_TEST_PARTNER_ID, 1); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.nbPage).to.eq(2); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); + }); + + it('should call the ID5 server with ab_testing object when abTesting is turned on', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: true, controlGroupPct: 0.234 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.ab_testing.enabled).to.eq(true); + expect(requestBody.ab_testing.control_group_pct).to.eq(0.234); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab_testing object when abTesting is turned off', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: false, controlGroupPct: 0.55 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.ab_testing).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab_testing when when abTesting is not set', function () { + let id5Config = getId5FetchConfig(); + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.ab_testing).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should store the privacy object from the ID5 server response', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + + let responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = { + jurisdiction: 'gdpr', + id5_consent: true + }; + request.respond(200, responseHeader, JSON.stringify(responseObject)); + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.eq(JSON.stringify(responseObject.privacy)); + coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + }); + + it('should not store a privacy object if not part of ID5 server response', function () { + coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.null; }); }); @@ -214,34 +311,39 @@ describe('ID5 ID System', function() { beforeEach(function() { sinon.stub(events, 'getEvents').returns([]); - coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); adUnits = [getAdUnitMock()]; }); afterEach(function() { events.getEvents.restore(); - coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); }); - it('should add stored ID from cookie to bids', function (done) { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + it('should add stored ID from cache to bids', function (done) { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, - uids: [{ id: ID5_STORED_ID, atype: 1 }] + uids: [{ + id: ID5_STORED_ID, + atype: 1, + ext: { + linkType: ID5_STORED_LINK_TYPE + } + }] }); }); }); @@ -258,7 +360,7 @@ describe('ID5 ID System', function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, uids: [{ id: ID5_STORED_ID, atype: 1 }] @@ -269,43 +371,40 @@ describe('ID5 ID System', function() { }, { adUnits }); }); - it('should set nb=1 in cookie when no stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + it('should set nb=1 in cache when no stored nb value exists and cached ID', function () { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(1); }); - it('should increment nb in cookie when stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + it('should increment nb in cache when stored nb value exists and cached ID', function () { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeNbInCache(ID5_TEST_PARTNER_ID, 1); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeInLocalStorage(`${ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); + storeNbInCache(ID5_TEST_PARTNER_ID, 1); - let id5Config = getFetchCookieConfig(); + let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; setSubmoduleRegistry([id5IdSubmodule]); @@ -313,9 +412,9 @@ describe('ID5 ID System', function() { config.setConfig(id5Config); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); expect(server.requests).to.be.empty; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); @@ -329,55 +428,81 @@ describe('ID5 ID System', function() { const responseHeader = { 'Content-Type': 'application/json' }; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(decodeURIComponent(getFromLocalStorage(ID5_STORAGE_NAME))).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); + }); - it('should call ID5 servers with 1puid and nb=1 post auction if refresh needed for legacy stored object', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_LEGACY_STORED_OBJ), expStr); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); + describe('Decode stored object', function() { + const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; - let id5Config = getFetchCookieConfig(); - id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; + it('should properly decode from a stored object', function() { + expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).to.deep.equal(expectedDecodedObject); + }); + it('should return undefined if passed a string', function() { + expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).to.eq(undefined); + }); + }); - setSubmoduleRegistry([id5IdSubmodule]); - init(config); - config.setConfig(id5Config); + describe('A/B Testing', function() { + const expectedDecodedObjectWithIdAbOff = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; + const expectedDecodedObjectWithIdAbOn = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false } } }; + const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: '', ext: { linkType: 0, abTestingControlGroup: true } } }; + let testConfig, storedObject; - let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + beforeEach(function() { + testConfig = getId5FetchConfig(); + storedObject = utils.deepClone(ID5_STORED_OBJ); + }); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); + describe('A/B Testing Config is Set', function() { + let randStub; - expect(server.requests).to.be.empty; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + beforeEach(function() { + randStub = sinon.stub(Math, 'random').callsFake(function() { + return 0.25; + }); + }); + afterEach(function () { + randStub.restore(); + }); - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(requestBody['1puid']).to.eq(ID5_STORED_ID); - expect(requestBody.nbPage).to.eq(1); + describe('Decode', function() { + let logErrorSpy; - const responseHeader = { 'Content-Type': 'application/json' }; - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + beforeEach(function() { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + afterEach(function() { + logErrorSpy.restore(); + }); - expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); - }); - }); + it('should not set abTestingControlGroup extension when A/B testing is off', function () { + let decoded = id5IdSubmodule.decode(storedObject, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); - describe('Decode stored object', function() { - const decodedObject = { 'id5id': ID5_STORED_ID }; + it('should set abTestingControlGroup to false when A/B testing is on but in normal group', function () { + storedObject.ab_testing = { result: 'normal' }; + let decoded = id5IdSubmodule.decode(storedObject, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + }); - it('should properly decode from a stored object', function() { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(decodedObject); - }); - it('should properly decode from a legacy stored object', function() { - expect(id5IdSubmodule.decode(ID5_LEGACY_STORED_OBJ)).to.deep.equal(decodedObject); - }); - it('should return undefined if passed a string', function() { - expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); + it('should not expose ID when everyone is in control group', function () { + storedObject.ab_testing = { result: 'control' }; + storedObject.universal_uid = ''; + storedObject.link_type = 0; + let decoded = id5IdSubmodule.decode(storedObject, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + }); + + it('should log A/B testing errors', function () { + storedObject.ab_testing = { result: 'error' }; + let decoded = id5IdSubmodule.decode(storedObject, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + sinon.assert.calledOnce(logErrorSpy); + }); + }); }); }); }); diff --git a/test/spec/modules/idImportLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js new file mode 100644 index 00000000000..699c2c43a94 --- /dev/null +++ b/test/spec/modules/idImportLibrary_spec.js @@ -0,0 +1,61 @@ +import * as utils from 'src/utils.js'; +import * as idImportlibrary from 'modules/idImportLibrary.js'; + +var expect = require('chai').expect; + +describe('currency', function () { + let fakeCurrencyFileServer; + let sandbox; + let clock; + + let fn = sinon.spy(); + + beforeEach(function () { + fakeCurrencyFileServer = sinon.fakeServer.create(); + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logError.restore(); + fakeCurrencyFileServer.restore(); + idImportlibrary.setConfig({}); + }); + + describe('setConfig', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + }); + + it('results when no config available', function () { + idImportlibrary.setConfig({}); + sinon.assert.called(utils.logError); + }); + it('results with config available', function () { + idImportlibrary.setConfig({ 'url': 'URL' }); + sinon.assert.called(utils.logInfo); + }); + it('results with config default debounce ', function () { + let config = { 'url': 'URL' } + idImportlibrary.setConfig(config); + expect(config.debounce).to.be.equal(250); + }); + it('results with config default fullscan ', function () { + let config = { 'url': 'URL' } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(false); + }); + it('results with config fullscan ', function () { + let config = { 'url': 'URL', 'fullscan': true } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + }); + }); +}); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 0d539d5988c..a31270c86c7 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -1,29 +1,36 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.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 pid = '14'; -const defaultConfigParams = {pid: pid}; +let defaultConfigParams; const responseHeader = {'Content-Type': 'application/json'} describe('IdentityLinkId tests', function () { let logErrorStub; beforeEach(function () { + defaultConfigParams = { params: {pid: pid} }; logErrorStub = sinon.stub(utils, 'logError'); + // remove _lr_retry_request cookie before test + storage.setCookie('_lr_retry_request', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); }); afterEach(function () { + defaultConfigParams = {}; logErrorStub.restore(); }); it('should log an error if no configParams were passed when getId', function () { - identityLinkSubmodule.getId(); + identityLinkSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; }); it('should log an error if pid configParam was not passed when getId', function () { - identityLinkSubmodule.getId({}); + identityLinkSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; }); @@ -41,7 +48,22 @@ describe('IdentityLinkId tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should call the LiveRamp envelope endpoint with consent string', function () { + it('should NOT call the LiveRamp envelope endpoint if gdpr applies but consent string is empty string', function () { + let consentData = { + gdprApplies: true, + consentString: '' + }; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData); + expect(submoduleCallback).to.be.undefined; + }); + + it('should NOT call the LiveRamp envelope endpoint if gdpr applies but consent string is missing', function () { + let consentData = { gdprApplies: true }; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData); + expect(submoduleCallback).to.be.undefined; + }); + + it('should call the LiveRamp envelope endpoint with IAB consent string v1', function () { let callBackSpy = sinon.spy(); let consentData = { gdprApplies: true, @@ -59,6 +81,27 @@ describe('IdentityLinkId tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); + it('should call the LiveRamp envelope endpoint with IAB consent string v2', function () { + let callBackSpy = sinon.spy(); + let consentData = { + gdprApplies: true, + consentString: 'CO4VThZO4VTiuADABBENAzCgAP_AAEOAAAAAAwwAgAEABhAAgAgAAA.YAAAAAAAAAA', + vendorData: { + tcfPolicyVersion: 2 + } + }; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=4&cv=CO4VThZO4VTiuADABBENAzCgAP_AAEOAAAAAAwwAgAEABhAAgAgAAA.YAAAAAAAAAA'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + it('should not throw Uncaught TypeError when envelope endpoint returns empty response', function () { let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; @@ -88,4 +131,38 @@ describe('IdentityLinkId tests', function () { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should not call the LiveRamp envelope endpoint if cookie _lr_retry_request exist', function () { + let now = new Date(); + now.setTime(now.getTime() + 3000); + storage.setCookie('_lr_retry_request', 'true', now.toUTCString()); + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request).to.be.eq(undefined); + }); + + it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist and notUse3P config property was not set', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should not call the LiveRamp envelope endpoint if config property notUse3P is set to true', function () { + defaultConfigParams.params.notUse3P = true; + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request).to.be.eq(undefined); + }); }); diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js new file mode 100644 index 00000000000..14cd9a88d13 --- /dev/null +++ b/test/spec/modules/idxIdSystem_spec.js @@ -0,0 +1,117 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { config } from 'src/config.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; + +const IDX_COOKIE_NAME = '_idx'; +const IDX_DUMMY_VALUE = 'idx value for testing'; +const IDX_COOKIE_STORED = '{ "idx": "' + IDX_DUMMY_VALUE + '" }'; +const ID_COOKIE_OBJECT = { id: IDX_DUMMY_VALUE }; +const IDX_COOKIE_OBJECT = { idx: IDX_DUMMY_VALUE }; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'idx' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('IDx ID System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('IDx: test "getId" method', () => { + it('provides the stored IDx if a cookie exists', () => { + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('provides the stored IDx if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('returns undefined if both cookie and local storage are empty', () => { + let idx = idxIdSubmodule.getId(); + expect(idx).to.be.undefined; + }) + }); + + describe('IDx: test "decode" method', () => { + it('provides the IDx from a stored object', () => { + expect(idxIdSubmodule.decode(ID_COOKIE_OBJECT)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + + it('provides the IDx from a stored string', () => { + expect(idxIdSubmodule.decode(IDX_DUMMY_VALUE)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + }); + + describe('requestBids hook', () => { + let adUnits; + + beforeEach(() => { + adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([idxIdSubmodule]); + init(config); + config.setConfig(getConfigMock()); + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + }); + + it('when a stored IDx exists it is added to bids', (done) => { + requestBidsHook(() => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idx'); + expect(bid.userId.idx).to.equal(IDX_DUMMY_VALUE); + const idxIdAsEid = find(bid.userIdAsEids, e => e.source == 'idx.lat'); + expect(idxIdAsEid).to.deep.equal({ + source: 'idx.lat', + uids: [{ + id: IDX_DUMMY_VALUE, + atype: 1, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); diff --git a/test/spec/modules/imRtdProvider_spec.js b/test/spec/modules/imRtdProvider_spec.js new file mode 100644 index 00000000000..58410dc0e38 --- /dev/null +++ b/test/spec/modules/imRtdProvider_spec.js @@ -0,0 +1,151 @@ +import { + imRtdSubmodule, + storage, + getCustomBidderFunction, + setRealTimeData, + getRealTimeData, + getApiCallback, + imUidLocalName, + imVidCookieName, + imRtdLocalName +} from 'modules/imRtdProvider.js' +import { timestamp } from '../../../src/utils.js' + +describe('imRtdProvider', function () { + let getLocalStorageStub; + let getCookieStub; + + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['test1', 'test2'] + } + ] + }; + const onDone = function() { return true }; + const moduleConfig = { + params: { + cid: 5126, + setGptKeyValues: true + } + } + + beforeEach(function (done) { + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getCookieStub = sinon.stub(storage, 'getCookie'); + done(); + }); + + afterEach(function () { + getLocalStorageStub.restore(); + getCookieStub.restore(); + }); + + describe('imRtdSubmodule', function () { + it('should initalise and return true', function () { + expect(imRtdSubmodule.init()).to.equal(true) + }) + }) + + describe('getCustomBidderFunction', function () { + it('should return config function', function () { + const config = { + params: { + overwrites: { + testBidder: function() { + return 'testString'; + } + } + } + }; + const bidder = 'testBidder' + expect(getCustomBidderFunction(config, bidder)).to.exist.and.to.be.a('function'); + expect(getCustomBidderFunction(config, bidder)()).to.equal('testString'); + }) + it('should return null when overwrites falsy', function () { + const config = { + params: { + overwrites: { + testBidder: null + } + } + }; + const bidder = 'testBidder' + expect(getCustomBidderFunction(config, bidder)).to.equal(null); + }) + }) + + describe('processBidderFunction', function () { + + }) + + describe('setRealTimeData', function () { + it('should return true when empty params', function () { + expect(setRealTimeData({adUnits: []}, {params: {}}, {im_segments: []})).to.equal(undefined) + }); + it('should return true when overwrites and bid params', function () { + const config = { + params: { + overwrites: { + testBidder: function() { return true } + } + } + }; + expect(setRealTimeData(testReqBidsConfigObj, config, {im_segments: []})).to.equal(undefined) + }); + }) + + describe('getRealTimeData', function () { + it('should initalise and return when empty params', function () { + expect(getRealTimeData({}, function() {}, {})).to.equal(undefined) + }); + + it('should initalise and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, moduleConfig)).to.equal(undefined) + }); + + it('should return the uid when sids(rtd) not expired', function () { + getLocalStorageStub.withArgs(imUidLocalName).returns('testUid'); + getLocalStorageStub.withArgs(imRtdLocalName).returns('testSids'); + getCookieStub.withArgs(imVidCookieName).returns('testUid'); + getLocalStorageStub.withArgs(`${imRtdLocalName}_mt`).returns(new Date(timestamp()).toUTCString()); + expect(getRealTimeData(testReqBidsConfigObj, onDone, moduleConfig)).to.equal(undefined) + }); + + it('should return the uid when it exists uid, sids(rtd), vid in storages and sids(rtd) expired', function () { + getLocalStorageStub.withArgs(imUidLocalName).returns('testUid'); + getLocalStorageStub.withArgs(imRtdLocalName).returns('testSids'); + getCookieStub.withArgs(imVidCookieName).returns('testUid'); + getLocalStorageStub.withArgs(`${imRtdLocalName}_mt`).returns(0); + expect(getRealTimeData(testReqBidsConfigObj, onDone, moduleConfig)).to.equal(undefined) + }); + + it('should return the uid when uid not expired', function () { + getLocalStorageStub.withArgs(imUidLocalName).returns('testUid'); + getLocalStorageStub.withArgs(imRtdLocalName).returns('testSids'); + getCookieStub.withArgs(imVidCookieName).returns('testUid'); + getLocalStorageStub.withArgs(`${imUidLocalName}_mt`).returns(new Date(timestamp()).toUTCString()); + expect(getRealTimeData(testReqBidsConfigObj, onDone, moduleConfig)).to.equal(undefined) + }); + }) + + describe('getApiCallback', function () { + it('should return success and error functions', function () { + const res = getApiCallback(testReqBidsConfigObj, false, moduleConfig); + expect(res.success).to.exist.and.to.be.a('function'); + expect(res.error).to.exist.and.to.be.a('function'); + }); + + it('should return "undefined" success', function () { + const res = getApiCallback(testReqBidsConfigObj, false, moduleConfig); + const successResponse = '{"uid": "testid", "segments": "testsegment", "vid": "testvid"}'; + expect(res.success(successResponse, {status: 200})).to.equal(undefined); + expect(res.error()).to.equal(undefined); + }); + + it('should return "undefined" catch error response', function () { + const res = getApiCallback(testReqBidsConfigObj, false, moduleConfig); + expect(res.success('error response', {status: 400})).to.equal(undefined); + }); + }) +}) diff --git a/test/spec/modules/imonomyBidAdapter_spec.js b/test/spec/modules/imonomyBidAdapter_spec.js deleted file mode 100644 index 45b3bed6e77..00000000000 --- a/test/spec/modules/imonomyBidAdapter_spec.js +++ /dev/null @@ -1,164 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/imonomyBidAdapter.js'; - -describe('Imonomy Adapter Tests', function () { - const bidsRequest = [ - { - bidder: 'imonomy', - params: { - placementId: '170577', - hbid: '14567718624', - }, - placementCode: 'div-gpt-ad-1460505748561-0', - transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', - sizes: [ - [300, 250] - ], - bidId: '2faedf1095f815', - bidderRequestId: '18065867f8ae39', - auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' - }, - { - bidder: 'imonomy', - params: { - placementId: '281277', - hbid: '14567718624', - floorPrice: 0.5 - }, - placementCode: 'div-gpt-ad-1460505748561-0', - transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', - sizes: [ - [728, 90] - ], - bidId: '3c34e2367a3f59', - bidderRequestId: '18065867f8ae39', - auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' - }]; - - const bidsResponse = { - body: { - bids: [ - { - placementid: '170577', - uuid: '2faedf1095f815', - width: 300, - height: 250, - cpm: 0.51, - creative: '', - ttl: 360, - currency: 'USD', - netRevenue: true, - creativeId: 'd30b58c2ba' - } - ] - } - }; - - it('Verifies imonomyAdapter bidder code', function () { - expect(spec.code).to.equal('imonomy'); - }); - - it('Verifies imonomyAdapter bid request validation', function () { - expect(spec.isBidRequestValid(bidsRequest[0])).to.equal(true); - expect(spec.isBidRequestValid(bidsRequest[1])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { hbid: 12345 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { placementid: 12345 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890, floorPrice: 0.8 } })).to.equal(true); - }); - - it('Verify imonomyAdapter build request', function () { - var startTime = new Date().getTime(); - - const request = spec.buildRequests(bidsRequest); - expect(request.url).to.equal('https://b.imonomy.com/openrtb/hb/14567718624'); - expect(request.method).to.equal('POST'); - const requestData = JSON.parse(request.data); - - // bids object - let bids = requestData.bids; - expect(bids).to.have.lengthOf(2); - - // first bid request: no floor price - expect(bids[0].uuid).to.equal('2faedf1095f815'); - expect(bids[0].floorprice).to.be.undefined; - expect(bids[0].placementid).to.equal('170577'); - expect(bids[0].hbid).to.equal('14567718624'); - expect(bids[0].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); - expect(bids[0].sizes).to.have.lengthOf(1); - expect(bids[0].sizes[0][0]).to.equal(300); - expect(bids[0].sizes[0][1]).to.equal(250); - - // second bid request: with floor price - expect(bids[1].uuid).to.equal('3c34e2367a3f59'); - expect(bids[1].floorprice).to.equal(0.5); - expect(bids[1].placementid).to.equal('281277'); - expect(bids[1].hbid).to.equal('14567718624'); - expect(bids[1].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); - expect(bids[1]).to.have.property('sizes') - .that.is.an('array') - .of.length(1) - .that.deep.equals([[728, 90]]); - - // kbConf object - let kbConf = requestData.kbConf; - expect(kbConf.hdbdid).to.equal(bids[0].hbid); - expect(kbConf.hdbdid).to.equal(bids[1].hbid); - expect(kbConf.encode_bid).to.be.undefined; - // kbConf timezone and cb - expect(kbConf.cb).not.to.be.undefined; - expect(kbConf.ts_as).to.be.above(startTime - 1); - expect(kbConf.tz).to.equal(new Date().getTimezoneOffset()); - // kbConf bid ids - expect(kbConf.hb_placement_bidids) - .to.have.property(bids[0].placementid) - .that.equal(bids[0].uuid); - expect(kbConf.hb_placement_bidids) - .to.have.property(bids[1].placementid) - .that.equal(bids[1].uuid); - // kbConf floor price - expect(kbConf.hb_floors).not.to.have.property(bids[0].placementid) - expect(kbConf.hb_floors).to.have.property(bids[1].placementid).that.equal(bids[1].floorprice); - // kbConf placement ids - expect(kbConf.hb_placements).to.have.lengthOf(2); - expect(kbConf.hb_placements[0]).to.equal(bids[0].placementid); - expect(kbConf.hb_placements[1]).to.equal(bids[1].placementid); - }); - - it('Verify imonomyAdapter build response', function () { - const request = spec.buildRequests(bidsRequest); - const bids = spec.interpretResponse(bidsResponse, request); - - // 'server' return single bid - expect(bids).to.have.lengthOf(1); - - // verify bid object - const bid = bids[0]; - const responseBids = bidsResponse.body.bids; - - expect(bid.cpm).to.equal(responseBids[0].cpm); - expect(bid.ad).to.equal(responseBids[0].creative); - expect(bid.requestId).equal(responseBids[0].uuid); - expect(bid.uuid).equal(responseBids[0].uuid); - expect(bid.width).to.equal(responseBids[0].width); - expect(bid.height).to.equal(responseBids[0].height); - expect(bid.ttl).to.equal(responseBids[0].ttl); - expect(bid.currency).to.equal('USD'); - expect(bid.netRevenue).to.equal(true); - expect(bid.creativeId).to.equal(responseBids[0].creativeId); - }); - - it('Verifies imonomyAdapter sync options', function () { - // user sync disabled - expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; - // user sync enabled - 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('https://b.imonomy.com/UserMatching/b/'); - }); -}); diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js new file mode 100644 index 00000000000..8bb2d089ad8 --- /dev/null +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -0,0 +1,413 @@ +import { expect } from 'chai'; +import { spec } from 'modules/impactifyBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; + +var gdprData = { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true +}; + +describe('ImpactifyAdapter', function () { + describe('isBidRequestValid', function () { + let validBid = { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.appId; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.appId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.format; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.format = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not equals to screen or display', () => { + const bid = utils.deepClone(validBid); + if (bid.params.format != 'screen' && bid.params.format != 'display') { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + + it('should return false when style is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.style; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when style is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.style = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002', + userId: { + pubcid: '87a0327b-851c-4bb3-a925-0c7be94548f5' + }, + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [ + { + id: '87a0327b-851c-4bb3-a925-0c7be94548f5', + atype: 1 + } + ] + } + ] + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.url).to.equal(ORIGIN + AUCTIONURI); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + hash: 'test', + expiry: 166192938, + meta: {'advertiserDomains': ['testdomain.com']}, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + let bidderRequest = { + bids: [ + { + bidId: '462c08f20d428', + adUnitCode: '/19968336/header-bid-tag-1', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + bidder: 'impactify', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let expectedResponse = [ + { + id: '65820304700829014', + requestId: '462c08f20d428', + cpm: 3.40, + currency: DEFAULT_CURRENCY, + netRevenue: true, + ad: '', + width: 1, + height: 1, + hash: 'test', + expiry: 166192938, + meta: {'advertiserDomains': ['testdomain.com']}, + ttl: 300, + creativeId: '97517771' + } + ]; + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }); + describe('getUserSyncs', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + let validResponse = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + hash: 'test', + expiry: 166192938, + meta: {'advertiserDomains': ['testdomain.com']}, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + it('should return empty response if server response is false', function () { + const result = spec.getUserSyncs('bad', false, gdprData); + expect(result).to.be.empty; + }); + it('should return empty response if server response is empty', function () { + const result = spec.getUserSyncs('bad', [], gdprData); + expect(result).to.be.empty; + }); + it('should append the various values if they exist', function() { + const result = spec.getUserSyncs({iframeEnabled: true}, validResponse, gdprData); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + }); + }); + + describe('On winning bid', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onBidWon(bid); + assert.ok(result); + }); + + describe('On bid Time out', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onTimeout(bid); + assert.ok(result); + }); +}) diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5a20944a6ed..095e50f0c66 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -28,6 +28,12 @@ describe('Improve Digital Adapter Tests', function () { sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] }; + const videoParams = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const instreamBidRequest = utils.deepClone(simpleBidRequest); instreamBidRequest.mediaTypes = { video: { @@ -154,6 +160,8 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); expect(params.bid_request.gdpr).to.not.exist; expect(params.bid_request.us_privacy).to.not.exist; + expect(params.bid_request.schain).to.not.exist; + expect(params.bid_request.user).to.not.exist; expect(params.bid_request.imp).to.deep.equal([ { id: '33e9500b21129f', @@ -255,6 +263,14 @@ describe('Improve Digital Adapter Tests', function () { params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); expect(params.bid_request.imp[0].bidfloorcur).to.equal('EUR'); + + // getFloor defined -> use it over bidFloor + let getFloorResponse = { currency: 'USD', floor: 3 }; + bidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].bidfloor).to.equal(3); + expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { @@ -278,14 +294,23 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); }); + it('should not add video params for banner', function () { + const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.not.exist; + }); + it('should add ad type for instream video', function () { - let bidRequest = Object.assign({}, simpleBidRequest); + let bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaType = 'video'; let request = spec.buildRequests([bidRequest], bidderRequest)[0]; let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + expect(params.bid_request.imp[0].video).to.not.exist; - bidRequest = Object.assign({}, simpleBidRequest); + bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaTypes = { video: { context: 'instream', @@ -295,18 +320,89 @@ describe('Improve Digital Adapter Tests', function () { request = spec.buildRequests([bidRequest], bidderRequest)[0]; params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + expect(params.bid_request.imp[0].video).to.not.exist; }); it('should not set ad type for outstream video', function() { const request = spec.buildRequests([outstreamBidRequest])[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.not.exist; + expect(params.bid_request.imp[0].video).to.not.exist; }); it('should not set ad type for multi-format bids', function() { const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.not.exist; + expect(params.bid_request.imp[0].video).to.not.exist; + }); + + it('should set video params for instream', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set skip params only if skip=1', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + bidRequest.params.video = videoTest; + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + + // 0 - leave out skipmin and skipafter + videoTest.skip = 0; + bidRequest.params.video = videoTest; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal({ skip: 0 }); + + // other + videoTest.skip = 'blah'; + bidRequest.params.video = videoTest; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.not.exist; + }); + + it('should ignore invalid/unexpected video params', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const videoTestInvParam = Object.assign({}, videoTest); + videoTestInvParam.blah = 1; + bidRequest.params.video = videoTestInvParam; + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + }); + + it('should set video params for outstream', function() { + const bidRequest = JSON.parse(JSON.stringify(outstreamBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set video params for multi-format', function() { + const bidRequest = JSON.parse(JSON.stringify(multiFormatBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); }); it('should not set Prebid sizes in bid request for instream video', function () { @@ -345,6 +441,22 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.schain).to.equal(schain); }); + it('should add eids', function () { + const userId = { id5id: { uid: '1111' } }; + const expectedUserObject = { ext: { eids: [{ + source: 'id5-sync.com', + uids: [{ + atype: 1, + id: '1111' + }] + }]}}; + const bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.userId = userId; + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.user).to.deep.equal(expectedUserObject); + }); + it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, @@ -646,7 +758,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -663,7 +774,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -679,7 +789,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -728,7 +837,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -853,6 +961,14 @@ describe('Improve Digital Adapter Tests', function () { expect(bids[0].netRevenue).to.equal(true); }); + it('should set advertiserDomains', function () { + const adomain = ['domain.com']; + const response = JSON.parse(JSON.stringify(serverResponse)); + response.body.bid[0].adomain = adomain; + const bids = spec.interpretResponse(response, {bidderRequest}); + expect(bids[0].meta.advertiserDomains).to.equal(adomain); + }); + // Native ads it('should return a well-formed native ad bid', function () { let bids = spec.interpretResponse(serverResponseNative, {bidderRequest}); diff --git a/test/spec/modules/imuIdSystem_spec.js b/test/spec/modules/imuIdSystem_spec.js new file mode 100644 index 00000000000..2934a7c213b --- /dev/null +++ b/test/spec/modules/imuIdSystem_spec.js @@ -0,0 +1,168 @@ +import { + imuIdSubmodule, + storage, + getApiUrl, + apiSuccessProcess, + getLocalData, + callImuidApi, + getApiCallback, + storageKey, + cookieKey, + apiUrl +} from 'modules/imuIdSystem.js'; + +import * as utils from 'src/utils.js'; + +describe('imuId module', function () { + // let setLocalStorageStub; + let getLocalStorageStub; + let getCookieStub; + // let ajaxBuilderStub; + + beforeEach(function (done) { + // setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getCookieStub = sinon.stub(storage, 'getCookie'); + // ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse('{}')); + done(); + }); + + afterEach(function () { + getLocalStorageStub.restore(); + getCookieStub.restore(); + // ajaxBuilderStub.restore(); + }); + + const storageTestCasesForEmpty = [ + undefined, + null, + '' + ] + + const configParamTestCase = { + params: { + cid: 5126 + } + } + + describe('getId()', function () { + it('should return the uid when it exists in local storages', function () { + getLocalStorageStub.withArgs(storageKey).returns('testUid'); + const id = imuIdSubmodule.getId(configParamTestCase); + expect(id).to.be.deep.equal({id: 'testUid'}); + }); + + storageTestCasesForEmpty.forEach(testCase => it('should return the callback when it not exists in local storages', function () { + getLocalStorageStub.withArgs(storageKey).returns(testCase); + const id = imuIdSubmodule.getId(configParamTestCase); + expect(id).have.all.keys('callback'); + })); + + it('should return "undefined" when empty param', function () { + const id = imuIdSubmodule.getId(); + expect(id).to.be.deep.equal(undefined); + }); + + it('should return the callback when it not exists in local storages (and has vid)', function () { + getCookieStub.withArgs(cookieKey).returns('test'); + const id = imuIdSubmodule.getId(configParamTestCase); + expect(id).have.all.keys('callback'); + }); + }); + + describe('getApiUrl()', function () { + it('should return default url when cid only', function () { + const url = getApiUrl(5126); + expect(url).to.be.equal(`${apiUrl}?cid=5126`); + }); + + it('should return param url when set url', function () { + const url = getApiUrl(5126, 'testurl'); + expect(url).to.be.equal('testurl?cid=5126'); + }); + }); + + describe('decode()', function () { + it('should return the uid when it exists in local storages', function () { + const id = imuIdSubmodule.decode('testDecode'); + expect(id).to.be.deep.equal({imuid: 'testDecode'}); + }); + + it('should return the undefined when decode id is not "string"', function () { + const id = imuIdSubmodule.decode(1); + expect(id).to.equal(undefined); + }); + }); + + describe('getLocalData()', function () { + it('always have the same key', function () { + getLocalStorageStub.withArgs(storageKey).returns('testid'); + getCookieStub.withArgs(cookieKey).returns('testvid'); + getLocalStorageStub.withArgs(`${storageKey}_mt`).returns(new Date(utils.timestamp()).toUTCString()); + const localData = getLocalData(); + expect(localData).to.be.deep.equal({ + id: 'testid', + vid: 'testvid', + expired: false + }); + }); + + it('should return expired is true', function () { + getLocalStorageStub.withArgs(`${storageKey}_mt`).returns(0); + const localData = getLocalData(); + expect(localData).to.be.deep.equal({ + id: undefined, + vid: undefined, + expired: true + }); + }); + }); + + describe('apiSuccessProcess()', function () { + it('should return the undefined when success response', function () { + const res = apiSuccessProcess({ + uid: 'test', + vid: 'test' + }); + expect(res).to.equal(undefined); + }); + + it('should return the undefined when empty response', function () { + const res = apiSuccessProcess(); + expect(res).to.equal(undefined); + }); + + it('should return the undefined when error response', function () { + const res = apiSuccessProcess({ + error: 'error response' + }); + expect(res).to.equal(undefined); + }); + }); + + describe('callImuidApi()', function () { + it('should return function when set url', function () { + const res = callImuidApi(`${apiUrl}?cid=5126`); + expect(res).to.exist.and.to.be.a('function'); + }); + }); + + describe('getApiCallback()', function () { + it('should return success and error functions', function () { + const res = getApiCallback(); + expect(res.success).to.exist.and.to.be.a('function'); + expect(res.error).to.exist.and.to.be.a('function'); + }); + + it('should return "undefined" success', function () { + const res = getApiCallback(function(uid) { return uid }); + expect(res.success('{"uid": "testid"}')).to.equal(undefined); + expect(res.error()).to.equal(undefined); + }); + + it('should return "undefined" catch error response', function () { + const res = getApiCallback(function(uid) { return uid }); + expect(res.success('error response')).to.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/inmarBidAdapter_spec.js b/test/spec/modules/inmarBidAdapter_spec.js new file mode 100644 index 00000000000..998fe20d369 --- /dev/null +++ b/test/spec/modules/inmarBidAdapter_spec.js @@ -0,0 +1,240 @@ +// 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/inmarBidAdapter.js'; +import {config} from 'src/config.js'; + +describe('Inmar adapter tests', function () { + var DEFAULT_PARAMS_NEW_SIZES = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'inmar', + params: { + partnerId: 12345 + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', // or 'outstream' + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'inmar', + params: { + partnerId: 12345 + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'inmar', + params: { + partnerId: 12345, + }, + auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var BID_RESPONSE = { + body: { + cpm: 1.50, + ad: '', + meta: { + mediaType: 'banner', + }, + width: 300, + height: 250, + creativeId: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + dealId: 'dealId' + + } + }; + + var BID_RESPONSE_VIDEO = { + body: { + cpm: 1.50, + meta: { + mediaType: 'video', + }, + width: 1, + height: 1, + creativeId: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + vastUrl: 'https://vast.com/vast.xml', + dealId: 'dealId' + } + }; + + it('Verify build request to prebid 3.0 display test', function() { + 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: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request.data); + expect(requestContent.bidRequests[0].params).to.have.property('partnerId').and.to.equal(12345); + expect(requestContent.bidRequests[0]).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); + expect(requestContent.bidRequests[0]).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); + expect(requestContent.bidRequests[0]).to.have.property('bidRequestsCount').and.to.equal(1); + expect(requestContent.bidRequests[0]).to.have.property('bidder').and.to.equal('inmar'); + expect(requestContent.bidRequests[0]).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); + expect(requestContent.bidRequests[0]).to.have.property('adUnitCode').and.to.equal('test-div'); + expect(requestContent.refererInfo).to.have.property('referer').and.to.equal('https://domain.com'); + expect(requestContent.bidRequests[0].mediaTypes.banner).to.have.property('sizes'); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[0]).to.have.ordered.members([300, 250]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[1]).to.have.ordered.members([300, 600]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[2]).to.have.ordered.members([728, 90]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[3]).to.have.ordered.members([970, 250]); + expect(requestContent.bidRequests[0]).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); + expect(requestContent.refererInfo).to.have.property('numIframes').and.to.equal(0); + }) + + it('Verify interprete response', function () { + 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: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.ad).to.equal(''); + expect(bid.meta.mediaType).to.equal('banner'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('no banner media response', function () { + 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: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request); + const bid = bids[0]; + expect(bid.vastUrl).to.equal('https://vast.com/vast.xml'); + }); + + it('Verifies bidder_code', function () { + expect(spec.code).to.equal('inmar'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('inm'); + }); + + it('Verifies if bid request is valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + partnerId: 12345 + } + })).to.equal(true); + }); + + it('Verifies user syncs image', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); +}); diff --git a/test/spec/modules/innityBidAdapter_spec.js b/test/spec/modules/innityBidAdapter_spec.js index 80c00252632..d4a28ec2100 100644 --- a/test/spec/modules/innityBidAdapter_spec.js +++ b/test/spec/modules/innityBidAdapter_spec.js @@ -37,9 +37,9 @@ describe('innityAdapterTest', () => { 'auctionId': '18fd8b8b0bd757' }]; - const bidderRequest = { + let bidderRequest = { refererInfo: { - referer: 'https://example.com' + referer: 'https://refererExample.com' } }; @@ -50,14 +50,21 @@ describe('innityAdapterTest', () => { }); }); - it('bidRequest data', () => { + it('bidRequest with complete data', () => { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].data.pub).to.equal(267); expect(requests[0].data.zone).to.equal(62546); expect(requests[0].data.width).to.equal('300'); expect(requests[0].data.height).to.equal('250'); + expect(requests[0].data.url).to.equal(encodeURIComponent('https://refererExample.com')); expect(requests[0].data.callback_uid).to.equal('51ef8751f9aead'); }); + + it('bidRequest without referer URL', () => { + delete bidderRequest.refererInfo; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.url).to.equal(''); + }); }); describe('interpretResponse', () => { @@ -74,12 +81,14 @@ describe('innityAdapterTest', () => { 'height': '250', 'callback': 'json', 'callback_uid': '51ef8751f9aead', - 'url': 'https://example.com', + 'url': 'https://refererExample.com', 'cb': '', } }; - const bidResponse = { + let advDomains = ['advertiserExample.com']; + + let bidResponse = { body: { 'cpm': 100, 'width': '300', @@ -87,6 +96,7 @@ describe('innityAdapterTest', () => { 'creative_id': '148186', 'callback_uid': '51ef8751f9aead', 'tag': '', + 'adomain': advDomains, }, headers: {} }; @@ -101,6 +111,14 @@ describe('innityAdapterTest', () => { expect(result[0].currency).to.equal('USD'); expect(result[0].ttl).to.equal(60); expect(result[0].ad).to.equal(''); + expect(result[0].meta.advertiserDomains).to.equal(advDomains); + }); + + it('result with no advertiser domain', () => { + bidResponse.body.adomain = []; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + expect(result[0].meta.advertiserDomains).to.deep.equal([]); }); }); }); diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js index e817b3e3b81..151cbce7692 100644 --- a/test/spec/modules/inskinBidAdapter_spec.js +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -250,7 +250,7 @@ describe('InSkin BidAdapter', function () { const payload = JSON.parse(request.data); expect(payload.keywords).to.be.an('array').that.is.empty; - expect(payload.placements[0].properties).to.be.undefined; + expect(payload.placements[0].properties.restrictions).to.be.undefined; }); it('should add keywords if TCF v2 purposes are not granted', function () { @@ -328,6 +328,7 @@ describe('InSkin BidAdapter', function () { expect(b).to.have.property('currency', 'USD'); expect(b).to.have.property('creativeId'); expect(b).to.have.property('ttl', 360); + expect(b.meta).to.have.property('advertiserDomains'); expect(b).to.have.property('netRevenue', true); }); }); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js new file mode 100644 index 00000000000..7764117dbae --- /dev/null +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -0,0 +1,416 @@ +import { expect } from 'chai'; +import { spec, storage } from '../../../modules/insticatorBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js' +import { userSync } from '../../../src/userSync.js'; + +const USER_ID_KEY = 'hb_insticator_uid'; +const USER_ID_DUMMY_VALUE = '74f78609-a92d-4cf1-869f-1b244bbfb5d2'; +const USER_ID_STUBBED = '12345678-1234-1234-1234-123456789abc'; + +let utils = require('src/utils.js'); + +describe('InsticatorBidAdapter', function () { + const adapter = newBidder(spec); + + let bidRequest = { + bidder: 'insticator', + adUnitCode: 'adunit-code', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + }; + + let bidderRequest = { + bidderRequestId: '22edbae2733bf6', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + timeout: 300, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {}, + gdprApplies: true + }, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'https://example.com', + stack: ['https://example.com'] + }, + }; + + describe('.code', function () { + it('should return a bidder code of insticator', function () { + expect(spec.code).to.equal('insticator') + }) + }) + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + it('should return true if the bid is valid', function () { + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }); + + it('should return false if there is no adUnitId param', () => { + expect(spec.isBidRequestValid({...bidRequest, ...{params: {}}})).to.be.false; + }); + + it('should return false if there is no mediaTypes', () => { + expect(spec.isBidRequestValid({...bidRequest, ...{mediaTypes: {}}})).to.be.false; + }); + + it('should return false if there are no banner sizes and no sizes', () => { + bidRequest.mediaTypes.banner = {}; + expect(spec.isBidRequestValid({...bidRequest, ...{sizes: {}}})).to.be.false; + }); + + it('should return true if there is sizes and no banner sizes', () => { + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }); + + it('should return true if there is banner sizes and no sizes', () => { + bidRequest.mediaTypes.banner.sizes = [[300, 250], [300, 600]]; + expect(spec.isBidRequestValid({...bidRequest, ...{sizes: {}}})).to.be.true; + }); + }); + + describe('buildRequests', function () { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + let sandbox; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'generateUUID').returns(USER_ID_STUBBED); + }); + + afterEach(() => { + sandbox.restore(); + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + const serverRequests = spec.buildRequests([bidRequest], bidderRequest); + it('should create a request', function () { + expect(serverRequests).to.have.length(1); + }); + + const serverRequest = serverRequests[0]; + it('should create a request object with method, URL, options and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.options).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('should return POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('should return valid URL', function () { + expect(serverRequest.url).to.equal('https://ex.ingage.tech/v1/openrtb'); + }); + + it('should return valid options', function () { + expect(serverRequest.options).to.be.an('object'); + expect(serverRequest.options.contentType).to.equal('application/json'); + expect(serverRequest.options.withCredentials).to.be.true; + }); + + it('should return valid data if array of bids is valid', function () { + localStorageIsEnabledStub.returns(true); + cookiesAreEnabledStub.returns(false); + localStorage.setItem(USER_ID_KEY, USER_ID_DUMMY_VALUE); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('id', 'tmax', 'source', 'site', 'device', 'regs', 'user', 'imp'); + expect(data.id).to.equal(bidderRequest.bidderRequestId); + expect(data.tmax).to.equal(bidderRequest.timeout); + expect(data.source).to.eql({ + fd: 1, + tid: bidderRequest.auctionId, + }); + expect(data.site).to.be.an('object'); + expect(data.site.domain).not.to.be.empty; + expect(data.site.page).not.to.be.empty; + expect(data.site.ref).to.equal(bidderRequest.refererInfo.referer); + expect(data.device).to.be.an('object'); + expect(data.device.w).to.equal(window.innerWidth); + expect(data.device.h).to.equal(window.innerHeight); + expect(data.device.js).to.equal(true); + expect(data.device.ext).to.be.an('object'); + expect(data.device.ext.localStorage).to.equal(true); + expect(data.device.ext.cookies).to.equal(false); + expect(data.regs).to.be.an('object'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.regs.ext.gdprConsentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.user).to.be.an('object'); + expect(data.user.id).to.equal(USER_ID_DUMMY_VALUE); + expect(data.imp).to.be.an('array').that.have.lengthOf(1); + expect(data.imp).to.deep.equal([{ + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + banner: { + format: [ + {w: 300, h: 250}, + {w: 300, h: 600}, + ] + }, + ext: { + insticator: { + adUnitId: bidRequest.params.adUnitId, + }, + } + }]); + }); + + it('should generate new userId if not valid user is stored', function () { + localStorageIsEnabledStub.returns(true); + localStorage.setItem(USER_ID_KEY, 'fake-user-id'); + + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + + expect(data.user.id).to.equal(USER_ID_STUBBED); + }); + it('should return empty regs object if no gdprConsent is passed', function () { + const requests = spec.buildRequests([bidRequest], {...bidderRequest, ...{gdprConsent: false}}); + const data = JSON.parse(requests[0].data); + expect(data.regs).to.be.an('object').that.is.empty; + }); + it('should return empty array if no valid requests are passed', function () { + expect(spec.buildRequests([], bidderRequest)).to.be.an('array').that.have.lengthOf(0); + }); + }); + + describe('interpretResponse', function () { + const bidRequests = { + method: 'POST', + url: 'https://ex.ingage.tech/v1/openrtb', + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: '', + bidderRequest: { + bidderRequestId: '22edbae2733bf6', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + timeout: 300, + bids: [ + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-1', + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid1', + }, + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-2', + sizes: [[120, 600], [300, 600], [160, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid2', + }, + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-3', + sizes: [[120, 600], [300, 600], [160, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid3', + } + ] + } + }; + + const bidResponse = { + body: { + id: '22edbae2733bf6', + bidid: 'foo9876', + cur: 'USD', + seatbid: [ + { + seat: 'some-dsp', + bid: [ + { + impid: 'bid1', + crid: 'crid1', + price: 0.5, + w: 300, + h: 200, + adm: 'adm1', + exp: 60, + bidADomain: ['test1.com'], + }, + { + impid: 'bid2', + crid: 'crid2', + price: 1.5, + w: 600, + h: 200, + adm: 'adm2', + bidADomain: ['test2.com'], + }, + { + impid: 'bid3', + crid: 'crid3', + price: 5.0, + w: 300, + h: 200, + adm: 'adm3', + bidADomain: ['test3.com'], + } + ], + }, + ] + } + }; + + const prebidResponse = [ + { + requestId: 'bid1', + creativeId: 'crid1', + cpm: 0.5, + currency: 'USD', + netRevenue: true, + ttl: 60, + width: 300, + height: 200, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'test1.com' + ] + }, + ad: 'adm1', + adUnitCode: 'adunit-code-1', + }, + { + requestId: 'bid2', + creativeId: 'crid2', + cpm: 1.5, + currency: 'USD', + netRevenue: true, + ttl: 300, + width: 600, + height: 200, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'test2.com' + ] + }, + ad: 'adm2', + adUnitCode: 'adunit-code-2', + }, + { + requestId: 'bid3', + creativeId: 'crid3', + cpm: 5.0, + currency: 'USD', + netRevenue: true, + ttl: 300, + width: 300, + height: 200, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'test3.com' + ] + }, + ad: 'adm3', + adUnitCode: 'adunit-code-3', + }, + ]; + + it('should map bidResponse to prebidResponse', function () { + const response = spec.interpretResponse(bidResponse, bidRequests); + response.forEach((resp, i) => { + expect(resp).to.deep.equal(prebidResponse[i]); + }); + }); + + it('should return empty response if bidderRequestId is invalid', function () { + const response = Object.assign({}, bidResponse); + response.body.id = 'fake-id'; + expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); + }); + + it('should return empty response if there is no seatbid array in response', function () { + const response = Object.assign({}, bidResponse); + delete response.body.seatbid; + expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse = [{ + body: { + ext: { + sync: [{ + code: 'so', + delay: 0 + }] + } + } + }]; + + it('should return one user sync', function () { + expect(spec.getUserSyncs({}, bidResponse)).to.deep.equal([{ + code: 'so', + delay: 0 + }]); + }) + + it('should return an empty array when sync is enabled but there are no bidResponses', function () { + expect(spec.getUserSyncs({}, [])).to.have.length(0); + }) + + it('should return an empty array when sync is enabled but no sync ext returned', function () { + const response = Object.assign({}, bidResponse[0]); + delete response.body.ext.sync; + expect(spec.getUserSyncs({}, [response])).to.have.length(0); + }) + }); +}); diff --git a/test/spec/modules/instreamTracking_spec.js b/test/spec/modules/instreamTracking_spec.js new file mode 100644 index 00000000000..8d795fec88b --- /dev/null +++ b/test/spec/modules/instreamTracking_spec.js @@ -0,0 +1,221 @@ +import { assert } from 'chai'; +import { trackInstreamDeliveredImpressions } from 'modules/instreamTracking.js'; +import { config } from 'src/config.js'; +import events from 'src/events.js'; +import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import { INSTREAM, OUTSTREAM } from 'src/video.js'; + +const BIDDER_CODE = 'sampleBidder'; +const VIDEO_CACHE_KEY = '4cf395af-8fee-4960-af0e-88d44e399f14'; + +let sandbox; + +function enableInstreamTracking(regex) { + let configStub = sandbox.stub(config, 'getConfig'); + configStub.withArgs('instreamTracking').returns(Object.assign( + { + enabled: true, + maxWindow: 10, + pollingFreq: 0 + }, + regex && {urlPattern: regex}, + )); +} + +function mockPerformanceApi({adServerCallSent, videoPresent}) { + let performanceStub = sandbox.stub(window.performance, 'getEntriesByType'); + let entries = [{ + name: 'https://domain.com/img.png', + initiatorType: 'img' + }, { + name: 'https://domain.com/script.js', + initiatorType: 'script' + }, { + name: 'https://domain.com/xhr', + initiatorType: 'xmlhttprequest' + }, { + name: 'https://domain.com/fetch', + initiatorType: 'fetch' + }]; + + if (adServerCallSent || videoPresent) { + entries.push({ + name: 'https://adserver.com/ads?custom_params=hb_uuid%3D' + VIDEO_CACHE_KEY + '%26pos%3D' + VIDEO_CACHE_KEY, + initiatorType: 'xmlhttprequest' + }); + } + + if (videoPresent) { + entries.push({ + name: 'https://prebid-vast-cache.com/cache?key=' + VIDEO_CACHE_KEY, + initiatorType: 'xmlhttprequest' + }); + } + + performanceStub.withArgs('resource').returns(entries); +} + +function mockBidResponse(adUnit, requestId) { + const bid = { + 'adUnitCod': adUnit.code, + 'bidderCode': adUnit.bids[0].bidder, + 'width': adUnit.sizes[0][0], + 'height': adUnit.sizes[0][1], + 'statusMessage': 'Bid available', + 'adId': 'id', + 'requestId': requestId, + 'source': 'client', + 'no_bid': false, + 'cpm': '1.1495', + 'ttl': 180, + 'creativeId': 'id', + 'netRevenue': true, + 'currency': 'USD', + } + if (adUnit.mediaTypes.video) { + bid.videoCacheKey = VIDEO_CACHE_KEY; + } + return bid +} + +function mockBidRequest(adUnit, bidResponse) { + return { + 'bidderCode': bidResponse.bidderCode, + 'auctionId': '20882439e3238c', + 'bidderRequestId': 'bidderRequestId', + 'bids': [ + { + 'adUnitCode': adUnit.code, + 'mediaTypes': adUnit.mediaTypes, + 'bidder': bidResponse.bidderCode, + 'bidId': bidResponse.requestId, + 'sizes': adUnit.sizes, + 'params': adUnit.bids[0].params, + 'bidderRequestId': 'bidderRequestId', + 'auctionId': '20882439e3238c', + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }; +} + +function getMockInput(mediaType) { + const bannerAdUnit = { + code: 'banner', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + sizes: [[300, 250]], + bids: [{bidder: BIDDER_CODE, params: {placementId: 'id'}}] + }; + const outStreamAdUnit = { + code: 'video-' + OUTSTREAM, + mediaTypes: {video: {playerSize: [640, 480], context: OUTSTREAM}}, + sizes: [[640, 480]], + bids: [{bidder: BIDDER_CODE, params: {placementId: 'id'}}] + }; + const inStreamAdUnit = { + code: 'video-' + INSTREAM, + mediaTypes: {video: {playerSize: [640, 480], context: INSTREAM}}, + sizes: [[640, 480]], + bids: [{bidder: BIDDER_CODE, params: {placementId: 'id'}}] + }; + + let adUnit; + switch (mediaType) { + default: + case 'banner': + adUnit = bannerAdUnit; + break; + case OUTSTREAM: + adUnit = outStreamAdUnit; + break; + case INSTREAM: + adUnit = inStreamAdUnit; + break; + } + + const bidResponse = mockBidResponse(adUnit, utils.getUniqueIdentifierStr()); + const bidderRequest = mockBidRequest(adUnit, bidResponse); + return { + adUnits: [adUnit], + bidsReceived: [bidResponse], + bidderRequests: [bidderRequest], + }; +} + +describe('Instream Tracking', function () { + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('gaurd checks', function () { + it('skip if tracking not enable', function () { + sandbox.stub(config, 'getConfig').withArgs('instreamTracking').returns(undefined); + assert.isNotOk(trackInstreamDeliveredImpressions({ + adUnits: [], + bidsReceived: [], + bidderRequests: [] + }), 'should not start tracking when tracking is disabled'); + }); + + it('run only if instream bids are present', function () { + enableInstreamTracking(); + assert.isNotOk(trackInstreamDeliveredImpressions({adUnits: [], bidsReceived: [], bidderRequests: []})); + }); + + it('checks for instream bids', function (done) { + enableInstreamTracking(); + assert.isNotOk(trackInstreamDeliveredImpressions(getMockInput('banner')), 'should not start tracking when banner bids are present') + assert.isNotOk(trackInstreamDeliveredImpressions(getMockInput(OUTSTREAM)), 'should not start tracking when outstream bids are present') + mockPerformanceApi({}); + assert.isOk(trackInstreamDeliveredImpressions(getMockInput(INSTREAM)), 'should start tracking when instream bids are present') + setTimeout(done, 10); + }); + }); + + describe('instream bids check', function () { + let spyEventsOn; + + beforeEach(function () { + spyEventsOn = sandbox.spy(events, 'emit'); + }); + + it('BID WON event is not emitted when no video cache key entries are present', function (done) { + enableInstreamTracking(); + trackInstreamDeliveredImpressions(getMockInput(INSTREAM)); + mockPerformanceApi({}); + setTimeout(function () { + assert.isNotOk(spyEventsOn.calledWith('bidWon')) + done() + }, 10); + }); + + it('BID WON event is not emitted when ad server call is sent', function (done) { + enableInstreamTracking(); + mockPerformanceApi({adServerCallSent: true}); + setTimeout(function () { + assert.isNotOk(spyEventsOn.calledWith('bidWon')) + done() + }, 10); + }); + + it('BID WON event is emitted when video cache key is present', function (done) { + enableInstreamTracking(/cache/); + const bidWonSpy = sandbox.spy(); + events.on('bidWon', bidWonSpy); + mockPerformanceApi({adServerCallSent: true, videoPresent: true}); + + trackInstreamDeliveredImpressions(getMockInput(INSTREAM)); + setTimeout(function () { + assert.isOk(spyEventsOn.calledWith('bidWon')) + assert(bidWonSpy.args[0][0].videoCacheKey, VIDEO_CACHE_KEY, 'Video cache key in bid won should be equal to video cache call'); + done() + }, 10); + }); + }); +}); diff --git a/test/spec/modules/integr8BidAdapter_spec.js b/test/spec/modules/integr8BidAdapter_spec.js new file mode 100644 index 00000000000..8c5a4b47903 --- /dev/null +++ b/test/spec/modules/integr8BidAdapter_spec.js @@ -0,0 +1,226 @@ +import { expect } from 'chai'; +import { spec, getBidFloor } from 'modules/integr8BidAdapter'; + +describe('integr8AdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId or placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: {} + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'integr8', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': '699229', + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://integr8.central.gjirafa.tech/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('728x90'); + }); + }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://integr8.central.gjirafa.tech/bid', + 'data': { + 'sizes': '728x90', + 'adUnitId': 'hb-leaderboard', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '
Test ad
', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360, + 'ADomain': ['somedomain.com'] + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad', + 'vastUrl', + 'mediaType', + 'meta' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function (key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + + it('all values correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal(728); + expect(result[0].height).to.equal(90); + expect(result[0].creativeId).to.equal('123abc'); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].netRevenue).to.equal(false); + expect(result[0].ttl).to.equal(360); + expect(result[0].referrer).to.equal('http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true'); + expect(result[0].ad).to.equal('
Test ad
'); + expect(result[0].meta.advertiserDomains).to.deep.equal(['somedomain.com']); + }) + }); + + describe('floor pricing', () => { + it('should return null when getFloor is not a function', () => { + const bid = { getFloor: 2 }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = { getFloor: () => 2 }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({ floor: 'string', currency: 'EUR' }) + }; + + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not EUR', () => { + const bid = { + getFloor: () => ({ floor: 5, currency: 'USD' }) + }; + + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({ floor: 5, currency: 'EUR' }) + }; + + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js new file mode 100644 index 00000000000..8ea30a6ba92 --- /dev/null +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -0,0 +1,181 @@ +import { expect } from 'chai'; +import {intentIqIdSubmodule, readData, FIRST_PARTY_KEY} from 'modules/intentIqIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; + +const partner = 10; +const pai = '11'; +const pcid = '12'; +const defaultConfigParams = { params: {partner: partner} }; +const paiConfigParams = { params: {partner: partner, pai: pai} }; +const pcidConfigParams = { params: {partner: partner, pcid: pcid} }; +const allConfigParams = { params: {partner: partner, pai: pai, pcid: pcid} }; +const responseHeader = {'Content-Type': 'application/json'} + +describe('IntentIQ tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if no configParams were passed when getId', function () { + let submodule = intentIqIdSubmodule.getId({ params: {} }); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should log an error if partner configParam was not passed when getId', function () { + let submodule = intentIqIdSubmodule.getId({ params: {} }); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should log an error if partner configParam was not a numeric value', function () { + let submodule = intentIqIdSubmodule.getId({ params: {partner: '10'} }); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should call the IntentIQ endpoint with only partner', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should ignore INVALID_ID and invalid responses in decode', function () { + // let resp = JSON.stringify({'RESULT': 'NA'}); + // expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined); + expect(intentIqIdSubmodule.decode('INVALID_ID')).to.equal(undefined); + expect(intentIqIdSubmodule.decode('')).to.equal(undefined); + expect(intentIqIdSubmodule.decode(undefined)).to.equal(undefined); + }); + + it('should call the IntentIQ endpoint with only partner, pai', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(paiConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the IntentIQ endpoint with only partner, pcid', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(pcidConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the IntentIQ endpoint with partner, pcid, pai', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should not throw Uncaught TypeError when IntentIQ endpoint returns empty response', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 204, + responseHeader, + '' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(request.response).to.equal(''); + }); + + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 503, + responseHeader, + 'Unavailable' + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('save result if ls=true', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({pid: 'test_pid', data: 'test_personid', ls: true}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('test_personid'); + }); + + it('dont save result if ls=false', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({pid: 'test_pid', data: 'test_personid', ls: false}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); + + it('save result as INVALID_ID on empty data and ls=true ', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({pid: 'test_pid', data: '', ls: true}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('INVALID_ID'); + }); +}); diff --git a/test/spec/modules/interactiveOffersBidAdapter_spec.js b/test/spec/modules/interactiveOffersBidAdapter_spec.js new file mode 100644 index 00000000000..ff9ca123def --- /dev/null +++ b/test/spec/modules/interactiveOffersBidAdapter_spec.js @@ -0,0 +1,43 @@ +import { expect } from 'chai'; +import {spec} from 'modules/interactiveOffersBidAdapter.js'; + +describe('Interactive Offers Prebbid.js Adapter', function() { + describe('isBidRequestValid function', function() { + let bid = {bidder: 'interactiveOffers', params: {partnerId: '100', tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}; + + it('returns true if all the required params are present and properly formatted', function() { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false if any if the required params is missing', function() { + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false if any if the required params is not properly formatted', function() { + bid.params = {partnerid: '100', tmax: 250}; + expect(spec.isBidRequestValid(bid)).to.be.false; + bid.params = {partnerId: '100', tmax: '+250'}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + describe('buildRequests function', function() { + let validBidRequests = [{bidder: 'interactiveOffers', params: {partnerId: '100', tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; + let bidderRequest = {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {partnerId: '100', tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://www.google.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://www.google.com'], canonicalUrl: null}}; + + it('returns a Prebid.js request object with a valid json string at the "data" property', function() { + let request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).length !== 0; + }); + }); + describe('interpretResponse function', function() { + let openRTBResponse = {body: [{cur: 'USD', id: '2052afa35febb79baa9893cc3ae8b83b89740df65fe98b1bd358dbae6e912801', seatbid: [{seat: 1493, bid: [{ext: {tagid: '227faa83f86546'}, crid: '24477', adm: '', nurl: '', adid: '1138', adomain: ['url.com'], price: '1.53', w: 300, h: 250, iurl: 'http://url.com', cat: ['IAB13-11'], id: '5507ced7a39c06942d3cb260197112ba712e4180', attr: [], impid: 1, cid: '13280'}]}], 'bidid': '0959b9d58ba71b3db3fa29dce3b117c01fc85de0'}], 'headers': {}}; + let prebidRequest = {method: 'POST', url: 'https://url.com', data: '{"id": "1aad860c-e04b-482b-acac-0da55ed491c8", "site": {"id": "url.com", "name": "url.com", "domain": "url.com", "page": "http://url.com", "ref": "http://url.com", "publisher": {"id": 100, "name": "http://url.com", "domain": "url.com"}, "content": {"language": "pt-PT"}}, "source": {"fd": 0, "tid": "1aad860c-e04b-482b-acac-0da55ed491c8", "pchain": ""}, "device": {"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36", "language": "pt-PT"}, "user": {}, "imp": [{"id":1, "secure": 0, "tagid": "227faa83f86546", "banner": {"pos": 0, "w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}}], "tmax": 300}', bidderRequest: {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {partnerId: '100', tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://url.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://url.com'], canonicalUrl: null}}}; + + it('returns an array of Prebid.js response objects', function() { + let prebidResponses = spec.interpretResponse(openRTBResponse, prebidRequest); + expect(prebidResponses).to.not.be.empty; + expect(prebidResponses[0].meta.advertiserDomains[0]).to.equal('url.com'); + }); + }); +}); diff --git a/test/spec/modules/invamiaBidAdapter_spec.js b/test/spec/modules/invamiaBidAdapter_spec.js new file mode 100644 index 00000000000..2f8f0612e44 --- /dev/null +++ b/test/spec/modules/invamiaBidAdapter_spec.js @@ -0,0 +1,172 @@ +import {expect} from 'chai'; +import {spec} from 'modules/invamiaBidAdapter.js'; + +describe('invamia bid adapter tests', function () { + describe('bid requests', function () { + it('should accept valid bid', function () { + const validBid = { + bidder: 'invamia', + params: {zoneId: 123}, + }; + + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should reject invalid bid', function () { + const invalidBid = { + bidder: 'invamia', + params: {}, + }; + + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should correctly build payload string', function () { + const bidRequests = [{ + bidder: 'invamia', + params: {zoneId: 123}, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + }]; + const payload = spec.buildRequests(bidRequests)[0].data; + + expect(payload).to.contain('ctype=div'); + expect(payload).to.contain('pzoneid=123'); + expect(payload).to.contain('width=300'); + expect(payload).to.contain('height=250'); + }); + + it('should support multiple bids', function () { + const bidRequests = [{ + bidder: 'invamia', + params: {zoneId: 123}, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + }, { + bidder: 'invamia', + params: {zoneId: 321}, + mediaTypes: { + banner: { + sizes: [[728, 90]], + }, + }, + bidId: '23acc48ad47af52', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba992', + bidderRequestId: '1c56ad30b9b8ca82', + transactionId: '92489f71-1bf2-49a0-adf9-000cea9347292', + }]; + const payload = spec.buildRequests(bidRequests); + + expect(payload).to.be.lengthOf(2); + }); + + it('should support multiple sizes', function () { + const bidRequests = [{ + bidder: 'invamia', + params: {zoneId: 123}, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + }]; + const payload = spec.buildRequests(bidRequests); + + expect(payload).to.be.lengthOf(2); + }); + }); + + describe('bid responses', function () { + it('should return complete bid response', function () { + const serverResponse = { + body: { + banner: { + hash: '1c56ad30b9b8ca8', + }, + hb: { + cpm: 0.5, + netRevenue: false, + adomains: ['securepubads.g.doubleclick.net'], + }, + template: { + html: '', + }, + }, + }; + const bidderRequest = { + bidId: '23acc48ad47af5', + params: { + requestedSizes: [300, 250], + }, + }; + + const bids = spec.interpretResponse(serverResponse, {bidderRequest}); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].requestId).to.equal('23acc48ad47af5'); + expect(bids[0].creativeId).to.equal('1c56ad30b9b8ca8'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].ttl).to.equal(600); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].netRevenue).to.equal(false); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].meta.advertiserDomains).to.be.lengthOf(1); + expect(bids[0].meta.advertiserDomains[0]).to.equal('securepubads.g.doubleclick.net'); + }); + + it('should return empty bid response', function () { + const serverResponse = { + body: {}, + }; + const bidderRequest = { + bidId: '23acc48ad47af5', + params: { + requestedSizes: [300, 250], + }, + }; + + const bids = spec.interpretResponse(serverResponse, {bidderRequest}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response 2', function () { + const serverResponse = { + body: { + template: { + html: '', + } + }, + }; + const bidderRequest = { + bidId: '23acc48ad47af5', + params: { + requestedSizes: [300, 250], + }, + }; + + const bids = spec.interpretResponse(serverResponse, {bidderRequest}); + + expect(bids).to.be.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index b75c6a4578f..ea3e1d6611b 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -22,7 +22,11 @@ describe('invibesBidAdapter:', function () { [400, 300], [125, 125] ], - transactionId: 't1' + transactionId: 't1', + userId: { + pubcid: 'pub-cid-code', + pubProvidedId: 'pub-provided-id' + } }, { bidId: 'b2', bidder: BIDDER_CODE, @@ -137,11 +141,19 @@ describe('invibesBidAdapter:', function () { }); it('has capped ids if local storage variable is correctly formatted', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; localStorage.ivvcap = '{"9731":[1,1768600800000]}'; const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); expect(request.data.capCounts).to.equal('9731=1'); }); + it('does not have capped ids if local storage variable is correctly formatted but no opt in', function () { + localStorage.ivvcap = '{"9731":[1,1768600800000]}'; + const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); + expect(request.data.capCounts).to.equal(''); + }); + it('does not have capped ids if local storage variable is incorrectly formatted', function () { localStorage.ivvcap = ':[1,1574334216992]}'; const request = spec.buildRequests(bidRequests, {auctionStart: Date.now()}); @@ -178,106 +190,143 @@ describe('invibesBidAdapter:', function () { expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[1].params.placementId); }); - it('uses cookies', function () { - global.document.cookie = 'ivNoCookie=1'; + it('sends undefined lid when no cookie', function () { let request = spec.buildRequests(bidRequests); expect(request.data.lId).to.be.undefined; }); - it('doesnt send the domain id if not graduated', function () { - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1522929537626,"hc":1}'; - let request = spec.buildRequests(bidRequests); - expect(request.data.lId).to.not.exist; - }); - - it('try to graduate but not enough count - doesnt send the domain id', function () { - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; - let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - }); - - it('try to graduate but not old enough - doesnt send the domain id', function () { - let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":5}'; - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - }); - - it('graduate and send the domain id', function () { - let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - stubDomainOptions(new StubbedPersistence('{"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}')); - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.exist; - }); - - it('send the domain id if already graduated', function () { - let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi"}')); - let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.exist; - expect(top.window.invibes.dom.tempId).to.exist; - }); - - it('send the domain id after replacing it with new format', function () { + it('sends lid when comes on cookie', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":0}'; let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; - expect(top.window.invibes.dom.tempId).to.exist; }); - it('dont send the domain id if consent declined on tcf v1', function () { + it('should send purpose 1', function () { let bidderRequest = { gdprConsent: { vendorData: { gdprApplies: true, hasGlobalConsent: false, - vendorConsents: {436: false} + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } } } }; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - expect(top.window.invibes.dom.tempId).to.not.exist; - expect(request.data.oi).to.equal(0); + expect(request.data.purposes.split(',')[0]).to.equal('true'); }); - it('dont send the domain id if consent declined on tcf v2', function () { + it('should send purpose 2 & 7', function () { let bidderRequest = { gdprConsent: { vendorData: { gdprApplies: true, hasGlobalConsent: false, - vendor: {consents: {436: false}} + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } } } }; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - expect(top.window.invibes.dom.tempId).to.not.exist; - expect(request.data.oi).to.equal(0); + expect(request.data.purposes.split(',')[1] && request.data.purposes.split(',')[6]).to.equal('true'); }); - it('dont send the domain id if no consent', function () { - let bidderRequest = {}; - stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + it('should send purpose 9', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - expect(top.window.invibes.dom.tempId).to.not.exist; + expect(request.data.purposes.split(',')[9]).to.equal('true'); }); - it('try to init id but was already loaded on page - does not increment the id again', function () { - let bidderRequest = {gdprConsent: {vendorData: {vendorConsents: {436: true}}}}; - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; + it('should send legitimateInterests 2 & 7', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + }, + legitimateInterests: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; let request = spec.buildRequests(bidRequests, bidderRequest); - request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.lId).to.not.exist; - expect(top.window.invibes.dom.tempId).to.exist; + expect(request.data.li.split(',')[1] && request.data.li.split(',')[6]).to.equal('true'); }); - it('should send oi = 0 when vendorData is null', function () { let bidderRequest = { gdprConsent: { @@ -315,7 +364,6 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when vendor consents for invibes are false on tcf v2', function () { let bidderRequest = { gdprConsent: { @@ -343,8 +391,76 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(0); }); + it('should send oi = 2 when vendor consent for invibes are false and vendor legitimate interest for invibes are true on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + it('should send oi = 0 when vendor consents and legitimate interests for invibes are false on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: false}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + it('should send oi = 0 when purpose consents is null', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}}, + purpose: {} + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); - it('should send oi = 0 when purpose consents weren\'t approved on tcf v2', function () { + it('should send oi = 2 when purpose consents weren\'t approved on tcf v2', function () { let bidderRequest = { gdprConsent: { vendorData: { @@ -369,10 +485,10 @@ describe('invibesBidAdapter:', function () { } }; let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.oi).to.equal(0); + expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when purpose consents are less then 10 on tcf v2', function () { + it('should send oi = 2 when purpose consents are less then 10 on tcf v2', function () { let bidderRequest = { gdprConsent: { vendorData: { @@ -392,7 +508,7 @@ describe('invibesBidAdapter:', function () { } }; let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.oi).to.equal(0); + expect(request.data.oi).to.equal(2); }); it('should send oi = 4 when vendor consents are null on tcf v2', function () { @@ -527,7 +643,7 @@ describe('invibesBidAdapter:', function () { expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when purpose consents weren\'t approved on tcf v1', function () { + it('should send oi = 2 when purpose consents weren\'t approved on tcf v1', function () { let bidderRequest = { gdprConsent: { vendorData: { @@ -545,10 +661,10 @@ describe('invibesBidAdapter:', function () { } }; let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.oi).to.equal(0); + expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when purpose consents are less then 5 on tcf v1', function () { + it('should send oi = 2 when purpose consents are less then 5 on tcf v1', function () { let bidderRequest = { gdprConsent: { vendorData: { @@ -564,7 +680,7 @@ describe('invibesBidAdapter:', function () { } }; let request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.oi).to.equal(0); + expect(request.data.oi).to.equal(2); }); it('should send oi = 0 when vendor consents for invibes are false on tcf v1', function () { @@ -617,9 +733,51 @@ describe('invibesBidAdapter:', function () { - ` + `, + meta: {} }]; + let multiResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '' + } + }] + }; + + let invalidResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }] + }] + }; + + let responseWithMeta = { + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '', + Meta: { + advertiserDomains: ['theadvertiser.com', 'theadvertiser_2.com'], + advertiserName: 'theadvertiser' + } + } + }; + context('when the response is not valid', function () { it('handles response with no bids requested', function () { let emptyResult = spec.interpretResponse({body: response}); @@ -665,29 +823,39 @@ describe('invibesBidAdapter:', function () { let emptyResult = spec.interpretResponse({BidModel: {}, Ads: [{BidPrice: 1}]}, {bidRequests}); expect(emptyResult).to.be.empty; }); + + it('handles response when bid model is missing', function () { + let emptyResult = spec.interpretResponse(invalidResponse); + expect(emptyResult).to.be.empty; + }); }); - context('when the response is valid', function () { - it('responds with a valid bid', function () { - top.window.invibes.setCookie('a', 'b', 370); - top.window.invibes.setCookie('c', 'd', 0); - let result = spec.interpretResponse({body: response}, {bidRequests}); + context('when the multiresponse is valid', function () { + it('responds with a valid multiresponse bid', function () { + let result = spec.interpretResponse({body: multiResponse}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('responds with a valid bid and uses logger', function () { - localStorage.InvibesDEBUG = true; + it('responds with a valid singleresponse bid', function () { let result = spec.interpretResponse({body: response}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); it('does not make multiple bids', function () { - localStorage.InvibesDEBUG = false; let result = spec.interpretResponse({body: response}, {bidRequests}); let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; }); }); + + context('when the response has meta', function () { + it('responds with a valid bid, with the meta info', function () { + let result = spec.interpretResponse({body: responseWithMeta}, {bidRequests}); + expect(result[0].meta.advertiserName).to.equal('theadvertiser'); + expect(result[0].meta.advertiserDomains).to.contain('theadvertiser.com'); + expect(result[0].meta.advertiserDomains).to.contain('theadvertiser_2.com'); + }); + }); }); describe('getUserSyncs', function () { @@ -705,6 +873,16 @@ describe('invibesBidAdapter:', function () { expect(response.url).to.include(SYNC_ENDPOINT); expect(response.url).to.include('optIn'); expect(response.url).to.include('ivvbks'); + }); + + it('returns an iframe with params including if enabled', function () { + top.window.invibes.optIn = 1; + global.document.cookie = 'ivvbks=17639.0,1,2;ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":0}'; + let response = spec.getUserSyncs({iframeEnabled: true}); + expect(response.type).to.equal('iframe'); + expect(response.url).to.include(SYNC_ENDPOINT); + expect(response.url).to.include('optIn'); + expect(response.url).to.include('ivvbks'); expect(response.url).to.include('ivbsdid'); }); diff --git a/test/spec/modules/iqmBidAdapter_spec.js b/test/spec/modules/iqmBidAdapter_spec.js new file mode 100644 index 00000000000..27693937330 --- /dev/null +++ b/test/spec/modules/iqmBidAdapter_spec.js @@ -0,0 +1,227 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import {spec} from 'modules/iqmBidAdapter'; + +const ENDPOINT = 'https://pbd.bids.iqm.com'; + +describe('iqmAdapter', 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: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.50 + }, + + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return false when no bid', function () { + expect(spec.isBidRequestValid()).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when it is video and mimes and protcol are not present', function () { + const bid = { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'a0aca162-e3d0-44db-a465-5c96a64fa5fb', + bidId: '2cbdc9b506be33', + bidRequestsCount: 1, + bidder: 'iqm', + bidderRequestId: '185c3a4c7f88ec', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: {pubcid: 'f56a553d-370d-4cea-b31a-7214a3d8f8e1'}, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [ + 640, + 480 + ] + ] + } + }, + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + geo: { + country: 'USA' + }, + + bidfloor: 0.50, + video: { + placement: 2, + mimes: null, + protocols: null, + skipppable: true, + playback_method: ['auto_play_sound_off'] + } + }, + src: 'client', + transactionId: 'a57d06fd-cc6d-4a90-87af-c10727998f0b' }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when required params are not found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + placementId: 0, + publisherId: null + + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validBidRequests = [ + {bidder: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.5}, + crumbs: { + pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, + fpd: {'context': {'pbAdSlot': '/19968336/header-bid-tag-0'}}, + mediaTypes: { + banner: { + sizes: [[300, 250]]}}, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', + sizes: [[300, 250]], + bidId: '266d810da21904', + bidderRequestId: '13c05d264c7ffe', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0}]; + + let bidderRequest = {bidderCode: 'iqm', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', bidderRequestId: '13c05d264c7ffe', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, fpd: {context: {pbAdSlot: '/19968336/header-bid-tag-0'}}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: '/19968336/header-bid-tag-0', transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', sizes: [[300, 250]], bidId: '266d810da21904', bidderRequestId: '13c05d264c7ffe', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615205942159, timeout: 7000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}, start: 1615205942162}; + + it('should parse out sizes', function () { + let temp = []; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = request[0].data; + + expect(payload.sizes).to.exist; + expect(payload.sizes[0]).to.deep.equal([300, 250]); + }); + + it('should populate the ad_types array on all requests', function () { + // const bidRequest = Object.assign({}, bidRequests[0]); + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = request[0].data; + + expect(payload.imp.mediatype).to.deep.equal('banner'); + }); + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request[0].url).to.equal(ENDPOINT); + expect(request[0].method).to.equal('POST'); + }); + it('should attach valid video params to the tag', function () { + let validBidRequests_video = [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; + let bidderRequest_video = {bidderCode: 'iqm', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', bidderRequestId: '16e1ce8481bc6d', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615271191985, timeout: 3000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], canonicalUrl: null}, start: 1615271191988}; + const request = spec.buildRequests(validBidRequests_video, bidderRequest_video); + const payload = request[0].data; + expect(payload.imp.id).to.exist; + expect(payload.imp.displaymanager).to.exist; + expect(payload.imp.displaymanagerver).to.exist; + + expect(payload.imp.video).to.deep.equal({ + context: 'instream', + w: 640, + h: 480, + mimes: ['video/mp4'], + placement: 1, + protocols: [2, 5], + startdelay: 0 + }); + }); + + it('should add referer info to payload', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = request[0].data; + + expect(payload.bidderRequest.refererInfo).to.exist; + expect(payload.bidderRequest.refererInfo).to.deep.equal({referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}); + }); + }) + + describe('interpretResponse', function () { + let tempResult = {requestId: '2d9601dd8328f8', currency: 'USD', cpm: 4.5, netRevenue: true, creativeId: 'cr-121004', adUnitCode: 'div-gpt-ad-1460505748561-0', 'auctionId': '22a4f3d8-511f-46ba-91be-53b9949e4b48', mediaType: 'banner', ttl: 3000, ad: " ", width: 844, height: 617}; + let validBidRequests_temp = [ + {bidder: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.5}, + crumbs: { + pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, + fpd: {'context': {'pbAdSlot': '/19968336/header-bid-tag-0'}}, + mediaTypes: { + banner: { + sizes: [[300, 250]]}}, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', + sizes: [[300, 250]], + bidId: '266d810da21904', + bidderRequestId: '13c05d264c7ffe', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0}]; + let bidderRequest = {bidderCode: 'iqm', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', bidderRequestId: '13c05d264c7ffe', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, fpd: {context: {pbAdSlot: '/19968336/header-bid-tag-0'}}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: '/19968336/header-bid-tag-0', transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', sizes: [[300, 250]], bidId: '266d810da21904', bidderRequestId: '13c05d264c7ffe', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615205942159, timeout: 7000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}, start: 1615205942162}; + let response = { + + id: '5bdbab92aae961cfbdf7465d', + seatbid: [{bid: [{id: 'bid-5bdbab92aae961cfbdf7465d-5bdbab92aae961cfbdf74653', impid: '5bdbab92aae961cfbdf74653', price: 9.9, nurl: 'https://winn.stage.iqm.com/smaato?raw=w9XViV4dovBHrxujHhBj-l-uWB08CUOMW_oR-EUxZbaWLL0ENzcMlP3CJFEURN6FgRp_HdjAjxTYHR7uG4S6h6dl_vjU_YNABiPd607-iTqxOCl-2cKLo-hhQus4sMw01VIqyqrPmzOTHTwJm4vTjUIoWMPZbARgQvUnBzjRH9xeYS-Bv3kgAW9NSBfgBZeLyT3WJJ_3VKIE_Iurt8OjpA%3D%3D&req_id=5bdbab92aae961cfbdf7465d&ap=${AUCTION_PRICE}', adm: " ", adomain: ['click.iqm.com'], iurl: 'https://d3jme5si7t6llb.cloudfront.net/image/1/404/owVo6mc_1588902031079.png', cid: '169218', crid: 'cr-301435', attr: [], h: 250, w: 250}]}], + bidid: '5bdbab92aae961cfbdf7465d' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + {requestId: '49ad5f21156efd', currency: 'USD', cpm: 9.9, netRevenue: true, creativeId: 'cr-301435', adUnitCode: '/19968336/header-bid-tag-0', auctionId: '853cddf1-8d13-4482-bd88-f5ef927d5ab3', mediaType: 'banner', ttl: 3000, ad: " ", width: 250, height: 250} + ]; + let temprequest = spec.buildRequests(validBidRequests_temp, bidderRequest); + + let result = spec.interpretResponse({ body: response }, temprequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + let validBidRequests_temp_video = + [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: 'cd86c3ff-d630-40e6-83ab-420e9e800594'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '8335b266-7a41-45f9-86a2-92fdc7cf0cd9', sizes: [[640, 480]], bidId: '26274beff25455', bidderRequestId: '17c5d8c3168761', auctionId: '2c592dcf-7dfc-4823-8203-dd1ebab77fe0', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; + let bidderRequest_video = {bidderCode: 'iqm', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', bidderRequestId: '16e1ce8481bc6d', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615271191985, timeout: 3000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], canonicalUrl: null}, start: 1615271191988}; + + it('handles non-banner media responses', function () { + let response = {id: '2341234', seatbid: [{bid: [{id: 'bid-2341234-1', impid: '1', price: 9, nurl: 'https://frontend.stage.iqm.com/static/vast-01.xml', adm: 'http://cdn.iqm.com/pbd?raw=312730_203cf73dc83fb_2824348636878_pbd', adomain: ['app1.stage.iqm.com'], cid: '168900', crid: 'cr-304503', attr: []}]}], bidid: '2341234'}; + + let temprequest_video = spec.buildRequests(validBidRequests_temp_video, bidderRequest_video); + + let result = spec.interpretResponse({ body: response }, temprequest_video[0]); + expect(result[0]).to.have.property('vastUrl'); + }); + }); +}); diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js new file mode 100644 index 00000000000..3c7da783728 --- /dev/null +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -0,0 +1,371 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/iqzoneBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'iqzone' + +describe('IQZoneBidAdapter', 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: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + 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 () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + 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://smartssp-us-east.iqzone.com/pbjs'); + }); + + 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', + '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.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 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); + + 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 () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + 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'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + 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 = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + 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', '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(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + 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'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + 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', 'meta'); + 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'); + 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 = { + 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; + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 680ffdc0fcc..6eff922536f 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3,9 +3,10 @@ import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('IndexexchangeAdapter', function () { - const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; @@ -18,7 +19,6 @@ describe('IndexexchangeAdapter', function () { 'sid': '00001', 'hp': 1 }, - { 'asi': 'indirectseller-2.com', 'sid': '00002', @@ -26,6 +26,129 @@ describe('IndexexchangeAdapter', function () { } ] }; + const LARGE_SET_OF_SIZES = [ + [300, 250], + [600, 410], + [336, 280], + [400, 300], + [320, 50], + [360, 360], + [250, 250], + [320, 250], + [400, 250], + [387, 359], + [300, 50], + [372, 250], + [320, 320], + [412, 412], + [327, 272], + [312, 260], + [384, 320], + [335, 250], + [366, 305], + [374, 250], + [375, 375], + [272, 391], + [364, 303], + [414, 414], + [366, 375], + [272, 360], + [364, 373], + [366, 359], + [320, 100], + [360, 250], + [468, 60], + [480, 300], + [600, 400], + [600, 300], + [33, 28], + [40, 30], + [32, 5], + [36, 36], + [25, 25], + [320, 25], + [400, 25], + [387, 35], + [300, 5], + [372, 20], + [320, 32], + [412, 41], + [327, 27], + [312, 26], + [384, 32], + [335, 25], + [366, 30], + [374, 25], + [375, 37], + [272, 31], + [364, 303], + [414, 41], + [366, 35], + [272, 60], + [364, 73], + [366, 59], + [320, 10], + [360, 25], + [468, 6], + [480, 30], + [600, 40], + [600, 30] + ]; + + const ONE_VIDEO = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + }, + size: [400, 100] + }, + sizes: [[400, 100]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const ONE_BANNER = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; const DEFAULT_BANNER_VALID_BID = [ { @@ -60,7 +183,9 @@ describe('IndexexchangeAdapter', function () { 'video/mp4', 'video/webm' ], - minduration: 0 + minduration: 0, + maxduration: 60, + protocols: [2] }, size: [400, 100] }, @@ -80,6 +205,66 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_MULTIFORMAT_BANNER_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250], + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [600, 700] + }, + banner: { + sizes: [[300, 250], [300, 600], [400, 500]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const DEFAULT_MULTIFORMAT_VIDEO_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [1] + }, + size: [300, 250] + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', @@ -110,6 +295,35 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + w: 300, + h: 250, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA' + }, + adm: '' + } + ], + seat: '3970' + } + ] + }; + const DEFAULT_VIDEO_BID_RESPONSE = { cur: 'USD', id: '1aa2bb3cc4de', @@ -192,8 +406,14 @@ describe('IndexexchangeAdapter', function () { const DEFAULT_USERID_DATA = { idl_env: '1234-5678-9012-3456', // Liveramp + netId: 'testnetid123', // NetId + IDP: 'userIDP000', // IDP + fabrickId: 'fabrickId9000', // FabrickId + uid2: { id: 'testuid2' } // UID 2.0 }; + const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); + const DEFAULT_USERID_PAYLOAD = [ { source: 'liveramp.com', @@ -203,6 +423,57 @@ describe('IndexexchangeAdapter', function () { rtiPartner: 'idl' } }] + }, { + source: 'netid.de', + uids: [{ + id: DEFAULT_USERID_DATA.netId, + ext: { + rtiPartner: 'NETID' + } + }] + }, { + source: 'neustar.biz', + uids: [{ + id: DEFAULT_USERID_DATA.fabrickId, + ext: { + rtiPartner: 'fabrickId' + } + }] + }, { + source: 'zeotap.com', + uids: [{ + id: DEFAULT_USERID_DATA.IDP, + ext: { + rtiPartner: 'zeotapIdPlus' + } + }] + }, { + source: 'uidapi.com', + uids: [{ + // when calling createEidsArray, UID2's getValue func returns .id, which is then set in uids + id: DEFAULT_USERID_DATA.uid2.id, + ext: { + rtiPartner: 'UID2' + } + }] + } + ]; + + const DEFAULT_USERID_BID_DATA = { + lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4', + flocId: { id: '1234', version: 'chrome.1.2' } + }; + + const DEFAULT_FLOC_USERID_PAYLOAD = [ + { + source: 'chrome.com', + uids: [{ + id: DEFAULT_USERID_BID_DATA.flocId.id, + ext: { + rtiPartner: 'flocId', + ver: DEFAULT_USERID_BID_DATA.flocId.version + } + }] } ]; @@ -258,10 +529,10 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when size is missing', function () { + it('should return True when size is missing ', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.params.size; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when size array is wrong length', function () { @@ -280,33 +551,47 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes is not banner or video', function () { + it('should return false when mediaTypes.banner does not have sizes', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes = { - native: { - sizes: [[300, 250]] + banner: { + size: [[300, 250]] } }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes.banner does not have sizes', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + it('should return false when mediaTypes.video.playerSize does not include params.size', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { - banner: { - size: [[300, 250]] + video: { + playerSize: [300, 250] } }; + bid.params.size = [100, 200]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes.video does not have sizes', function () { + it('should return true when mediaTypes.video.playerSize includes params.size', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { video: { - size: [[300, 250]] + playerSize: [[300, 250], [200, 300]] } }; + bid.params.size = [[300, 250]]; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when bid.params.size is missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when minduration is missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.minduration; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -341,6 +626,16 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true for banner bid when there are multiple mediaTypes (banner, outstream)', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true for video bid when there are multiple mediaTypes (banner, outstream)', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -366,6 +661,71 @@ describe('IndexexchangeAdapter', function () { bid.params.bidFloorCur = 70; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required video properties are missing on both adunit & param levels', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.mimes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required video properties are at the adunit level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.mimes; + bid.mediaTypes.video.mimes = ['video/mp4']; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true if protocols exists but protocol doesn\'t', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.protocols; + bid.mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true if protocol exists but protocols doesn\'t', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.protocols; + bid.params.video.protocol = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.protocol; + bid.mediaTypes.video.protocol = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if both protocol/protocols are missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.protocols; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('Roundel alias adapter', function () { + const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; + const ALIAS_OPTIONS = Object.assign({ + bidderCode: 'roundel' + }, DEFAULT_OPTION); + + it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { + vaildBids.forEach((validBid) => { + const request = spec.buildRequests(validBid, ALIAS_OPTIONS); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(0); + }); + }); + + it('should build requests for mediaTypes if liveramp data is avaliable', function () { + vaildBids.forEach((validBid) => { + const cloneValidBid = utils.deepClone(validBid); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); + const payload = JSON.parse(request[0].data.r); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf.above(0); // should be 1 or more + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + }); + }); }); describe('buildRequestsIdentity', function () { @@ -555,15 +915,108 @@ describe('IndexexchangeAdapter', function () { delete window.headertag; }); - it('IX adapter reads LiveRamp IDL envelope from Prebid and adds it to Video', function () { + it('IX adapter reads supported user modules from Prebid and adds it to Video', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + }); + it('IX adapter reads floc id from prebid userId and adds it to eids when there is not other eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(1); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id from prebid userId and appends it to eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads empty floc obj from prebid userId it, floc is not added to eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = { 'flocId': {} } + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it version is missing, floc is not added to eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = { 'flocId': { 'id': 'abcd' } } + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it ID is missing, floc is not added to eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = { 'flocId': { 'version': 'chrome.a.b.c' } } + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id with empty id from prebid userId and it does not added to eids', function () { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = { flocID: { id: '', ver: 'chrome.1.2.3' } }; + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { @@ -617,14 +1070,47 @@ describe('IndexexchangeAdapter', function () { } } ] - } - }; - - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA) - - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); + }, + NetIdIp: { + source: 'netid.de', + uids: [ + { + id: 'testnetid', + ext: { + rtiPartner: 'NETID' + } + } + ] + }, + NeustarIp: { + source: 'neustar.biz', + uids: [ + { + id: 'testfabrick', + ext: { + rtiPartner: 'fabrickId' + } + } + ] + }, + ZeotapIp: { + source: 'zeotap.com', + uids: [ + { + id: 'testzeotap', + ext: { + rtiPartner: 'zeotapIdPlus' + } + } + ] + } + }; + + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); validUserIdPayload.push({ @@ -662,10 +1148,15 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(3); + expect(payload.user.eids).to.have.lengthOf(7); + expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[6]); }); it('IXL and Prebid are mutually exclusive', function () { @@ -687,7 +1178,7 @@ describe('IndexexchangeAdapter', function () { }; const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; @@ -705,14 +1196,173 @@ describe('IndexexchangeAdapter', function () { }); const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(2); + expect(payload.user.eids).to.have.lengthOf(6); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); + }); + }); + + describe('getUserIds', function () { + it('request should contain userId information if configured and within bid request', function () { + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { name: 'lotamePanoramaId' }, + { name: 'merkleId' }, + { name: 'parrableId' }, + ] + } + }); + + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.userId = DEFAULT_USERID_BID_DATA; + + const request = spec.buildRequests([bid], DEFAULT_OPTION)[0]; + const r = JSON.parse(request.data.r); + + expect(r.ext.ixdiag.userIds).to.be.an('array'); + expect(r.ext.ixdiag.userIds.should.include('lotamePanoramaId')); + expect(r.ext.ixdiag.userIds.should.not.include('merkleId')); + expect(r.ext.ixdiag.userIds.should.not.include('parrableId')); + }); + }); + + describe('First party data', function () { + afterEach(function () { + config.setConfig({ + ortb2: {} + }) + }); + + it('should not set ixdiag.fpd value if not defined', function () { + config.setConfig({ + ortb2: {} + }); + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = JSON.parse(request.data.r); + + expect(r.ext.ixdiag.fpd).to.be.undefined; + }); + + it('should set ixdiag.fpd value if it exists using fpd', function () { + config.setConfig({ + fpd: { + site: { + data: { + pageType: 'article' + } + } + } + }); + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = JSON.parse(request.data.r); + + expect(r.ext.ixdiag.fpd).to.exist; + }); + + it('should set ixdiag.fpd value if it exists using ortb2', function () { + config.setConfig({ + ortb2: { + site: { + ext: { + data: { + pageType: 'article' + } + } + } + } + }); + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = JSON.parse(request.data.r); + + expect(r.ext.ixdiag.fpd).to.exist; + }); + + it('should not send information that is not part of openRTB spec v2.5 using fpd', function () { + config.setConfig({ + fpd: { + site: { + keywords: 'power tools, drills', + search: 'drill', + testProperty: 'test_string' + }, + user: { + keywords: ['a'], + testProperty: 'test_string' + } + } + }); + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = JSON.parse(request.data.r); + + expect(r.site.keywords).to.exist; + expect(r.site.search).to.exist; + expect(r.site.testProperty).to.be.undefined; + expect(r.user.keywords).to.exist; + expect(r.user.testProperty).to.be.undefined; + }); + + it('should not send information that is not part of openRTB spec v2.5 using ortb2', function () { + config.setConfig({ + ortb2: { + site: { + keywords: 'power tools, drills', + search: 'drill', + testProperty: 'test_string' + }, + user: { + keywords: ['a'], + testProperty: 'test_string' + } + } + }); + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = JSON.parse(request.data.r); + + expect(r.site.keywords).to.exist; + expect(r.site.search).to.exist; + expect(r.site.testProperty).to.be.undefined; + expect(r.user.keywords).to.exist; + expect(r.user.testProperty).to.be.undefined; + }); + + it('should not add fpd data to r object if it exceeds maximum request', function () { + config.setConfig({ + ortb2: { + site: { + keywords: 'power tools, drills', + search: 'drill', + }, + user: { + keywords: Array(1000).join('#'), + } + } + }); + + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + + const request = spec.buildRequests([bid])[0]; + const r = JSON.parse(request.data.r); + + expect(r.site.ref).to.exist; + expect(r.site.keywords).to.be.undefined; + expect(r.user).to.be.undefined; }); }); describe('buildRequests', function () { - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + let request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; const query = request.data; @@ -722,12 +1372,6 @@ describe('IndexexchangeAdapter', function () { const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; const queryWithoutSchain = requestWithoutSchain.data; - const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID); - delete bidWithoutMediaType[0].mediaTypes; - bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]]; - const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_OPTION)[0]; - const queryWithoutMediaType = requestWithoutMediaType.data; - it('request should be made to IX endpoint with GET method', function () { expect(requestMethod).to.equal('GET'); expect(requestUrl).to.equal(IX_SECURE_ENDPOINT); @@ -742,18 +1386,66 @@ describe('IndexexchangeAdapter', function () { expect(query.nf).not.to.exist; }); + it('should send dfp_adunit_code in request if ortb2Imp.ext.data.adserver.adslot exists', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const { dfp_ad_unit_code } = JSON.parse(requests[0].data.r).imp[0].banner.format[0].ext; + expect(dfp_ad_unit_code).to.equal(AD_UNIT_CODE); + }); + + it('should not send dfp_adunit_code in request if ortb2Imp.ext.data.adserver.adslot does not exists', function () { + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const { dfp_ad_unit_code } = JSON.parse(requests[0].data.r).imp[0].banner.format[0].ext; + + expect(dfp_ad_unit_code).to.not.exist; + }); + it('payload should have correct format and value', function () { const payload = JSON.parse(query.r); expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); - expect(payload.site).to.exist; + expect(payload.id).to.be.a('string'); expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); - expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); - expect(payload.imp).to.exist; expect(payload.imp).to.be.an('array'); - expect(payload.imp).to.have.lengthOf(2); + expect(payload.imp).to.have.lengthOf(1); + }); + + it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { + const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); + bidWithIntId[0].bidderRequestId = 123456; + + request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; + + const payload = JSON.parse(request.data.r); + expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); + expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); + expect(payload.id).to.be.a('string'); + }); + + it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { + const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); + bidWithIntId[0].bidderRequestId = 123456; + + request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; + + const payload = JSON.parse(request.data.r); + expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); + expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); + expect(payload.id).to.be.a('string'); }); it('payload should not include schain when not provided', function () { @@ -763,57 +1455,141 @@ describe('IndexexchangeAdapter', function () { it('impression should have correct format and value', function () { const impression = JSON.parse(query.r).imp[0]; - const sidValue = `${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(impression.banner).to.exist; - expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impression.banner.topframe).to.exist; + expect(impression.banner.format).to.be.length(2); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); - }); - it('impression should have bidFloor and bidFloorCur if configured', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid.params.bidFloor = 50; - bid.params.bidFloorCur = 'USD'; - const requestBidFloor = spec.buildRequests([bid])[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + impression.banner.format.map(({ w, h, ext }, index) => { + const size = DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); - expect(impression.bidfloor).to.equal(bid.params.bidFloor); - expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(ext.sid).to.equal(sidValue); + }); }); - it('payload without mediaType should have correct format and value', function () { - const payload = JSON.parse(queryWithoutMediaType.r); + describe('build requests with price floors', () => { + const highFloor = 4.5; + const lowFloor = 3.5; + const currency = 'USD'; - expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); - expect(payload.site).to.exist; - expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); - expect(payload.site.ref).to.equal(document.referrer); - expect(payload.ext).to.exist; - expect(payload.ext.source).to.equal('prebid'); - expect(payload.imp).to.exist; - expect(payload.imp).to.be.an('array'); - expect(payload.imp).to.have.lengthOf(1); - }); + it('video impression should contain floors from priceFloors module', function () { + const bid = utils.deepClone(ONE_VIDEO[0]); + const expectedFloor = 3.25; + bid.getFloor = () => ({ floor: expectedFloor, currency }); + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; - it('impression without mediaType should have correct format and value', function () { - const impression = JSON.parse(queryWithoutMediaType.r).imp[0]; - const sidValue = `${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`; + expect(impression.bidfloor).to.equal(expectedFloor); + expect(impression.bidfloorcur).to.equal(currency); + expect(impression.ext.fl).to.equal('p'); + }); - expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(impression.banner).to.exist; - expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impression.banner.topframe).to.exist; - expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); + it('banner impression should contain floors from priceFloors module', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) + const expectedFloor = 3.25; + bid.getFloor = () => ({ floor: expectedFloor, currency }); + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(expectedFloor); + expect(impression.bidfloorcur).to.equal(currency); + expect(impression.banner.format[0].ext.fl).to.equal('p'); + }); + + it('should default to ix floors when priceFloors Module is not implemented', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.params.bidFloor = highFloor; + bid.params.bidFloorCur = 'USD' + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(highFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.banner.format[0].ext.fl).to.equal('x'); + }); + + it('should prioritize priceFloors Module over IX param floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.params.bidFloor = lowFloor; + bid.params.bidFloorCur = 'USD'; + const expectedFloor = highFloor; + bid.getFloor = () => ({ floor: expectedFloor, currency }); + const requestBidFloor = spec.buildRequests([bid])[0]; + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(highFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.banner.format[0].ext.fl).to.equal('p'); + }); + + it('impression should have bidFloor and bidFloorCur if configured', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const requestBidFloor = spec.buildRequests([bid])[0]; + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(impression.bidfloor).to.equal(bid.params.bidFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.banner.format[0].ext.fl).to.equal('x'); + }); + + it('missing sizes impressions should contain floors from priceFloors module ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.mediaTypes.banner.sizes.push([500, 400]) + + const expectedFloor = 3.25; + bid.getFloor = () => ({ floor: expectedFloor, currency }); + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + expect(bid.getFloor.getCall(1).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(1).args[0].size[0]).to.equal(500); + expect(bid.getFloor.getCall(1).args[0].size[1]).to.equal(400); + + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(impression.bidfloor).to.equal(expectedFloor); + expect(impression.bidfloorcur).to.equal(currency); + }); + + it('banner impressions should have pricefloors in AdUnit', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const expectedFloor = 4.3; + bid.floors = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size'] + }, + values: { + 'banner|300x250': expectedFloor, + 'banner|600x500': 6.5, + 'banner|*': 7.5 + } + }; + bid.getFloor = () => ({ floor: expectedFloor, currency }); + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(impression.bidfloor).to.equal(expectedFloor); + expect(impression.bidfloorcur).to.equal(currency); + }); }); it('impression should have sid if id is configured as number', function () { @@ -823,14 +1599,11 @@ describe('IndexexchangeAdapter', function () { const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(impression.banner).to.exist; - expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impression.banner.topframe).to.exist; + expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal('50'); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.sid).to.equal('50'); }); it('impression should have sid if id is configured as string', function () { @@ -838,109 +1611,215 @@ describe('IndexexchangeAdapter', function () { bid.params.id = 'abc'; const requestBidFloor = spec.buildRequests([bid])[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(impression.banner).to.exist; - expect(impression.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impression.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impression.banner.topframe).to.exist; + expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal('abc'); - }); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.sid).to.equal('abc'); + }); + + describe('first party data', () => { + it('should add first party data to page url in bid request if it exists in config', function () { + config.setConfig({ + ix: { + firstPartyData: { + ab: 123, + cd: '123#ab', + 'e/f': 456, + 'h?g': '456#cd' + } + } + }); - it('should add first party data to page url in bid request if it exists in config', function () { - config.setConfig({ - ix: { - firstPartyData: { - ab: 123, - cd: '123#ab', - 'e/f': 456, - 'h?g': '456#cd' + const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; + const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + expect(pageUrl).to.equal(expectedPageUrl); + }); + + it('should not set first party data if it is not an object', function () { + config.setConfig({ + ix: { + firstPartyData: 500 } - } + }); + + const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; + + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); }); - const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; - const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + it('should not set first party or timeout if it is not present', function () { + config.setConfig({ + ix: {} + }); - expect(pageUrl).to.equal(expectedPageUrl); - }); + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - it('should not set first party data if it is not an object', function () { - config.setConfig({ - ix: { - firstPartyData: 500 - } + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(requestWithoutConfig.data.t).to.be.undefined; }); - const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; + it('should not set first party or timeout if it is setConfig is not called', function () { + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(requestWithoutConfig.data.t).to.be.undefined; + }); - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + it('should set timeout if publisher set it through setConfig', function () { + config.setConfig({ + ix: { + timeout: 500 + } + }); + const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + + expect(requestWithTimeout.data.t).to.equal(500); + }); + + it('should set timeout if timeout is a string', function () { + config.setConfig({ + ix: { + timeout: '500' + } + }); + const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + + expect(requestStringTimeout.data.t).to.be.undefined; + }); }); - it('should not set first party or timeout if it is not present', function () { - config.setConfig({ - ix: {} + describe('request should contain both banner and video requests', function () { + const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); + + it('should have banner request', () => { + const bannerImpression = JSON.parse(request[0].data.r).imp[0]; + + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImpression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + + expect(bannerImpression.banner.format).to.be.length(2); + expect(bannerImpression.banner.topframe).to.be.oneOf([0, 1]); + + bannerImpression.banner.format.map(({ w, h, ext }, index) => { + const size = DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); + + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(ext.sid).to.equal(sidValue); + }); }); - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + it('should have video request', () => { + const videoImpression = JSON.parse(request[1].data.r).imp[0]; - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); - expect(requestWithoutConfig.data.t).to.be.undefined; + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImpression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(videoImpression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImpression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); + }); }); - it('should not set first party or timeout if it is setConfig is not called', function () { - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + it('single request under 8k size limit for large ad unit', function () { + const options = {}; + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + const requests = spec.buildRequests([bid], options); - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); - expect(requestWithoutConfig.data.t).to.be.undefined; + const reqSize = `${requests[0].url}?${utils.parseQueryStringParameters(requests[0].data)}`.length; + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); + expect(reqSize).to.be.lessThan(8000); + expect(requests[0].data.sn).to.be.undefined; }); - it('should set timeout if publisher set it through setConfig', function () { - config.setConfig({ - ix: { - timeout: 500 - } - }); - const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + it('2 requests due to 2 ad units, one larger than url size', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid.params.siteId = '124'; + bid.adUnitCode = 'div-gpt-1' + bid.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid.bidId = '2f6g5s5e'; - expect(requestWithTimeout.data.t).to.equal(500); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(2); + expect(requests[0].data.sn).to.be.equal(0); + expect(requests[1].data.sn).to.be.equal(1); }); - it('should set timeout if timeout is a string', function () { - config.setConfig({ - ix: { - timeout: '500' - } - }); - const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + it('6 ad units should generate only 4 requests', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid1.params.siteId = '121'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = 'tr1'; + bid1.bidId = '2f6g5s5e'; + + const bid2 = utils.deepClone(bid1); + bid2.transactionId = 'tr2'; + + const bid3 = utils.deepClone(bid1); + bid3.transactionId = 'tr3'; + + const bid4 = utils.deepClone(bid1); + bid4.transactionId = 'tr4'; + + const bid5 = utils.deepClone(bid1); + bid5.transactionId = 'tr5'; + + const bid6 = utils.deepClone(bid1); + bid6.transactionId = 'tr6'; + + const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); + + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(4); - expect(requestStringTimeout.data.t).to.be.undefined; + for (var i = 0; i < requests.length; i++) { + const reqSize = `${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`.length; + expect(reqSize).to.be.lessThan(8000); + let payload = JSON.parse(requests[i].data.r); + if (requests.length > 1) { + expect(requests[i].data.sn).to.equal(i); + } + expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); + } }); - it('request should contain both banner and video requests', function () { - const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); + it('multiple ad units in one request', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = [[300, 250], [300, 600], [100, 200]]; + bid1.params.siteId = '121'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = 'tr1'; + bid1.bidId = '2f6g5s5e'; - const bannerImp = JSON.parse(request[0].data.r).imp[0]; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(2); - expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); - expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); - expect(bannerImp.banner).to.exist; - expect(bannerImp.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(bannerImp.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + const bid2 = utils.deepClone(bid1); + bid2.transactionId = 'tr2'; + bid2.mediaTypes.banner.sizes = [[220, 221], [222, 223], [300, 250]]; + const bid3 = utils.deepClone(bid1); + bid3.transactionId = 'tr3'; + bid3.mediaTypes.banner.sizes = [[330, 331], [332, 333], [300, 250]]; - const videoImp = JSON.parse(request[1].data.r).imp[0]; - expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); - expect(videoImp.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(videoImp.video).to.exist; - expect(videoImp.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); - expect(videoImp.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); + const requests = spec.buildRequests([bid1, bid2, bid3], DEFAULT_OPTION); + + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); + + const impressions = JSON.parse(requests[0].data.r).imp; + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(3); + expect(requests[0].data.sn).to.be.undefined; }); it('request should contain the extra banner ad sizes that IX is not configured for using the first site id in the ad unit', function () { @@ -953,24 +1832,21 @@ describe('IndexexchangeAdapter', function () { bid2.params.bidId = '2b3c4d5e'; const request = spec.buildRequests([bid, bid2], DEFAULT_OPTION)[0]; - const impressions = JSON.parse(request.data.r).imp; + const impression = JSON.parse(request.data.r).imp[0]; - expect(impressions).to.be.an('array'); - expect(impressions).to.have.lengthOf(4); + expect(impression.id).to.equal(bid.bidId); + expect(impression.banner.format).to.be.length(bid.mediaTypes.banner.sizes.length); + expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[1].ext.siteID).to.equal(bid2.params.siteId) - expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[3].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) + impression.banner.format.map(({ w, h, ext }, index) => { + const size = bid.mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); - expect(impressions[0].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impressions[0].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impressions[1].banner.w).to.equal(bid2.params.size[0]); - expect(impressions[1].banner.h).to.equal(bid2.params.size[1]); - expect(impressions[2].banner.w).to.equal(bid.mediaTypes.banner.sizes[2][0]); - expect(impressions[2].banner.h).to.equal(bid.mediaTypes.banner.sizes[2][1]); - expect(impressions[3].banner.w).to.equal(bid.mediaTypes.banner.sizes[3][0]); - expect(impressions[3].banner.h).to.equal(bid.mediaTypes.banner.sizes[3][1]); + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(index === 1 ? bid2.params.siteId : bid.params.siteId); + expect(ext.sid).to.equal(sidValue); + }); }); it('request should contain the extra banner ad sizes and their corresponding site ids when there is multiple ad units', function () { @@ -983,30 +1859,30 @@ describe('IndexexchangeAdapter', function () { bid.sizes = [[336, 280], [970, 90]] bid.mediaTypes.banner.sizes = [[336, 280], [970, 90]] - const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], bid], DEFAULT_OPTION)[0]; + const bids = [DEFAULT_BANNER_VALID_BID[0], bid]; + const request = spec.buildRequests(bids, DEFAULT_OPTION)[0]; const impressions = JSON.parse(request.data.r).imp; expect(impressions).to.be.an('array'); - expect(impressions).to.have.lengthOf(4); + expect(impressions).to.have.lengthOf(2); + expect(request.data.sn).to.be.undefined; + + impressions.map((impression, impressionIndex) => { + const firstSizeObject = bids[impressionIndex].mediaTypes.banner.sizes[0]; - expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[1].ext.siteID).to.equal(bid.params.siteId) - expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[3].ext.siteID).to.equal(bid.params.siteId) + expect(impression.banner.format).to.be.length(2); + expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impressions[0].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); - expect(impressions[0].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); - expect(impressions[1].banner.w).to.equal(bid.params.size[0]); - expect(impressions[1].banner.h).to.equal(bid.params.size[1]); - expect(impressions[2].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0]); - expect(impressions[2].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1]); - expect(impressions[3].banner.w).to.equal(bid.mediaTypes.banner.sizes[1][0]); - expect(impressions[3].banner.h).to.equal(bid.mediaTypes.banner.sizes[1][1]); + impression.banner.format.map(({ w, h, ext }, index) => { + const size = bids[impressionIndex].mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); - expect(impressions[0].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`); - expect(impressions[1].ext.sid).to.equal(`${bid.params.size[0].toString()}x${bid.params.size[1].toString()}`); - expect(impressions[2].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0].toString()}x${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1].toString()}`); - expect(impressions[3].ext.sid).to.equal(`${bid.mediaTypes.banner.sizes[1][0].toString()}x${bid.mediaTypes.banner.sizes[1][1].toString()}`); + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(bids[impressionIndex].params.siteId.toString()); + expect(ext.sid).to.equal(sidValue); + }); + }); }); it('request should not contain the extra video ad sizes that IX is not configured for', function () { @@ -1016,6 +1892,28 @@ describe('IndexexchangeAdapter', function () { expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(1); }); + + describe('detect missing sizes', function () { + beforeEach(function () { + config.setConfig({ + ix: { + detectMissingSizes: false + } + }); + }) + + it('request should not contain missing sizes if detectMissingSizes = false', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + + const requests = spec.buildRequests([bid1, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + + const impressions = JSON.parse(requests[0].data.r).imp; + + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(1); + }); + }); }); describe('buildRequestVideo', function () { @@ -1033,55 +1931,180 @@ describe('IndexexchangeAdapter', function () { it('impression should have correct format and value', function () { const impression = JSON.parse(query.r).imp[0]; - const sidValue = `${DEFAULT_VIDEO_VALID_BID[0].params.size[0].toString()}x${DEFAULT_VIDEO_VALID_BID[0].params.size[1].toString()}`; + const sidValue = utils.parseGPTSingleSizeArray(DEFAULT_VIDEO_VALID_BID[0].params.size); expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(impression.video).to.exist; expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); expect(impression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); - expect(impression.video.placement).to.exist; expect(impression.video.placement).to.equal(1); expect(impression.video.minduration).to.exist; expect(impression.video.minduration).to.equal(0); - expect(impression.video.mimes).to.exist; expect(impression.video.mimes[0]).to.equal('video/mp4'); expect(impression.video.mimes[1]).to.equal('video/webm'); expect(impression.video.skippable).to.equal(false); - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); }); - it('impression should have correct format when mediaType is specified.', function () { + it('should not use default placement values when placement is defined at adUnit level', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); - delete bid.mediaTypes; - bid.mediaType = 'video'; - const requestBidFloor = spec.buildRequests([bid])[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; - const sidValue = `${DEFAULT_VIDEO_VALID_BID[0].params.size[0].toString()}x${DEFAULT_VIDEO_VALID_BID[0].params.size[1].toString()}`; + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.placement = 2; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(impression.video).to.exist; - expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); - expect(impression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); - expect(impression.video.placement).to.not.exist; - expect(impression.ext).to.exist; - expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); - expect(impression.ext.sid).to.equal(sidValue); + expect(impression.video.placement).to.equal(2); }); - it('should set correct placement if context is outstream', function () { + it('should set correct default placement, if context is instream', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'instream'; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video.placement).to.equal(1); + }); + + it('should set correct default placement, if context is outstream', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; const request = spec.buildRequests([bid])[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(impression.video).to.exist; - expect(impression.video.placement).to.exist; expect(impression.video.placement).to.equal(4); }); + + it('should handle unexpected context', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'not-valid'; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + expect(impression.video.placement).to.be.undefined; + }); + + it('should not override video properties if they are already configured at the params video level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.protocols = [1]; + bid.mediaTypes.video.mimes = ['video/override']; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.protocols[0]).to.equal(2); + expect(impression.video.mimes[0]).to.not.equal('video/override'); + }); + + it('should not add video adunit level properties in imp object if they are not allowlisted', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.random = true; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.random).to.not.exist; + }); + + it('should add allowlisted adunit level video properties in imp object if they are not configured at params level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + delete bid.params.video.protocols; + delete bid.params.video.mimes; + bid.mediaTypes.video.protocols = [6]; + bid.mediaTypes.video.mimes = ['video/mp4']; + bid.mediaTypes.video.api = 2; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.protocols[0]).to.equal(6); + expect(impression.video.api).to.equal(2); + expect(impression.video.mimes[0]).to.equal('video/mp4'); + }); + }); + + describe('buildRequestMultiFormat', function () { + it('only banner bidder params set', function () { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID) + const bannerImpression = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImpression.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); + expect(bannerImpression.banner.format[0].w).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[0]); + expect(bannerImpression.banner.format[0].h).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[1]); + }); + + describe('only video bidder params set', function () { + it('should generate video impression', function () { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID); + const videoImp = JSON.parse(request[1].data.r).imp[0]; + expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + }); + + it('should get missing sizes count 0 when params.size not used', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + delete bid.params.size; + const request = spec.buildRequests([bid]); + const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; + expect(diagObj.msd).to.equal(0); + expect(diagObj.msi).to.equal(0); + }); + }); + + describe('both banner and video bidder params set', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + const request = spec.buildRequests(bids); + + it('should return valid banner requests', function () { + const impressions = JSON.parse(request[0].data.r).imp; + + expect(impressions).to.have.lengthOf(2); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + + impressions.map((impression, index) => { + const bid = bids[index]; + + expect(impression.id).to.equal(bid.bidId); + expect(impression.banner.format).to.be.length(bid.mediaTypes.banner.sizes.length); + expect(impression.banner.topframe).to.be.oneOf([0, 1]); + + impression.banner.format.map(({ w, h, ext }, index) => { + const size = bid.mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); + + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(bid.params.siteId.toString()); + expect(ext.sid).to.equal(sidValue); + }); + }); + }); + + it('should return valid banner and video requests', function () { + const videoImpression = JSON.parse(request[1].data.r).imp[0]; + + expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImpression.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImpression.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0]); + expect(videoImpression.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[1]); + }); + + it('should contain all correct IXdiag properties', function () { + const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; + expect(diagObj.iu).to.equal(0); + expect(diagObj.nu).to.equal(0); + expect(diagObj.ou).to.equal(2); + expect(diagObj.ren).to.equal(false); + expect(diagObj.mfu).to.equal(2); + expect(diagObj.allu).to.equal(2); + expect(diagObj.version).to.equal('$prebid.version$'); + }); + }); }); describe('interpretResponse', function () { @@ -1098,7 +2121,31 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should get correct bid response for banner ad with missing adomain', function () { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, meta: { networkId: 50, brandId: 303325, @@ -1106,7 +2153,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -1125,15 +2172,15 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); }); it('should set Japanese price correctly', function () { @@ -1151,21 +2198,22 @@ describe('IndexexchangeAdapter', function () { currency: 'JPY', ttl: 300, netRevenue: true, - dealId: undefined, meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); expect(result[0]).to.deep.equal(expectedParse[0]); }); - it('should set dealId correctly', function () { + it('should prioritize bid[].dealid over bid[].ext.dealid ', function () { const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); - bidResponse.seatbid[0].bid[0].ext.dealid = 'deal'; + bidResponse.seatbid[0].bid[0].ext.dealid = 'ext-deal'; + bidResponse.seatbid[0].bid[0].dealid = 'outter-deal'; const expectedParse = [ { requestId: '1a2b3c4d', @@ -1178,18 +2226,74 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 300, netRevenue: true, - dealId: 'deal', + dealId: 'outter-deal', meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + + expect(result[0].dealId).to.equal(expectedParse[0].dealId); + }); + + it('should not set bid[].dealid if dealid is not present', function () { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should use set bid[].ext.dealid if bid[].dealid is not present', function () { + const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + bidResponse.seatbid[0].bid[0].ext.dealid = 'ext-deal'; + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + dealId: 'ext-deal', + netRevenue: true, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + expect(result[0].dealId).to.deep.equal(expectedParse[0].dealId); + }); + it('should get correct bid response for video ad', function () { const expectedParse = [ { @@ -1197,21 +2301,35 @@ describe('IndexexchangeAdapter', function () { cpm: 1.1, creativeId: '12346', mediaType: 'video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [ + 400, + 100 + ] + ] + } + }, width: 640, height: 480, currency: 'USD', ttl: 3600, netRevenue: true, - dealId: undefined, vastUrl: 'www.abcd.com/vast', meta: { networkId: 51, brandId: 303326, - brandName: 'OECTB' + brandName: 'OECTB', + advertiserDomains: ['www.abcd.com'] } } ]; - const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: ONE_VIDEO + }); + expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -1247,6 +2365,26 @@ describe('IndexexchangeAdapter', function () { expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); + + it('should set bid[].ttl to seatbid[].bid[].exp value from response', function () { + const BANNER_RESPONSE_WITH_EXP = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); + const VIDEO_RESPONSE_WITH_EXP = utils.deepClone(DEFAULT_VIDEO_BID_RESPONSE); + VIDEO_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 200; + BANNER_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 100; + const bannerResult = spec.interpretResponse({ body: BANNER_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const videoResult = spec.interpretResponse({ body: VIDEO_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + + expect(bannerResult[0].ttl).to.equal(100); + expect(videoResult[0].ttl).to.equal(200); + }); + + it('should default bid[].ttl if seat[].bid[].exp is not in the resposne', function () { + const bannerResult = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const videoResult = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + + expect(bannerResult[0].ttl).to.equal(300); + expect(videoResult[0].ttl).to.equal(3600); + }); }); describe('bidrequest consent', function () { @@ -1326,5 +2464,54 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); + + it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + const options = { + gdprConsent: { + consentString: '3huaa11=qu3198ae', + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + const options = { + gdprConsent: { + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; + }); + + it('should set coppa to 1 in config when enabled', () => { + config.setConfig({ coppa: true }) + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.equal(1); + }); + it('should not set coppa in config when disabled', () => { + config.setConfig({ coppa: false }) + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.be.undefined; + }); + it('should not set coppa when not specified in config', () => { + config.resetConfig(); + const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const r = JSON.parse(bid[0].data.r); + + expect(r.regs.coppa).to.be.undefined; + }); }); }); diff --git a/test/spec/modules/jcmBidAdapter_spec.js b/test/spec/modules/jcmBidAdapter_spec.js deleted file mode 100644 index 9d84bca5513..00000000000 --- a/test/spec/modules/jcmBidAdapter_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/jcmBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -const ENDPOINT = 'https://media.adfrontiers.com/'; - -describe('jcmAdapter', 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': 'jcm', - 'params': { - 'siteId': '3608' - }, - '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; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'jcm', - 'params': { - 'siteId': '3608' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - - ]; - - const request = spec.buildRequests(bidRequests); - - it('sends bid request to ENDPOINT via GET', function () { - expect(request.method).to.equal('GET'); - }); - - it('sends correct bid parameters', function () { - const payloadArr = request.data.split('&'); - expect(request.method).to.equal('GET'); - expect(payloadArr.length).to.equal(4); - expect(payloadArr[0]).to.equal('t=hb'); - expect(payloadArr[1]).to.equal('ver=1.0'); - expect(payloadArr[2]).to.equal('compact=true'); - const adReqStr = request.data.split('&bids=')[1]; - const adReq = JSON.parse(decodeURIComponent(adReqStr)); - const adReqBid = JSON.parse(decodeURIComponent(adReqStr)).bids[0]; - expect(adReqBid.siteId).to.equal('3608'); - expect(adReqBid.callbackId).to.equal('30b31c1838de1e'); - expect(adReqBid.adSizes).to.equal('300x250,300x600'); - }); - }); - - describe('interpretResponse', function () { - it('should get correct bid response', function () { - let serverResponse = {'bids': [{'width': 300, 'height': 250, 'creativeId': '29681110', 'ad': '', 'cpm': 0.5, 'callbackId': '30b31c1838de1e'}]}; - - let expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'bidderCode': 'jcm', - 'cpm': 0.5, - 'creativeId': '29681110', - 'width': 300, - 'height': 250, - 'ttl': 60, - 'currency': 'USA', - 'netRevenue': true, - 'ad': '', - } - ]; - - let result = spec.interpretResponse({ body: serverResponse }); - expect(Object.keys(result[0]).length).to.equal(Object.keys(expectedResponse[0]).length); - expect(Object.keys(result[0]).requestId).to.equal(Object.keys(expectedResponse[0]).requestId); - expect(Object.keys(result[0]).bidderCode).to.equal(Object.keys(expectedResponse[0]).bidderCode); - expect(Object.keys(result[0]).cpm).to.equal(Object.keys(expectedResponse[0]).cpm); - expect(Object.keys(result[0]).creativeId).to.equal(Object.keys(expectedResponse[0]).creativeId); - expect(Object.keys(result[0]).width).to.equal(Object.keys(expectedResponse[0]).width); - expect(Object.keys(result[0]).height).to.equal(Object.keys(expectedResponse[0]).height); - expect(Object.keys(result[0]).ttl).to.equal(Object.keys(expectedResponse[0]).ttl); - expect(Object.keys(result[0]).currency).to.equal(Object.keys(expectedResponse[0]).currency); - expect(Object.keys(result[0]).netRevenue).to.equal(Object.keys(expectedResponse[0]).netRevenue); - - expect(Object.keys(result[0]).ad).to.equal(Object.keys(expectedResponse[0]).ad); - }); - - it('handles nobid responses', function () { - let serverResponse = {'bids': []}; - - let result = spec.interpretResponse({ body: serverResponse }); - expect(result.length).to.equal(0); - }); - }); - describe('getUserSyncs', function () { - it('Verifies sync iframe option', 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('https://media.adfrontiers.com/hb/jcm_usersync.html'); - }); - - it('Verifies sync image option', function () { - expect(spec.getUserSyncs({ image: false })).to.be.undefined; - const options = spec.getUserSyncs({ image: true }); - expect(options).to.not.be.undefined; - expect(options).to.have.lengthOf(1); - expect(options[0].type).to.equal('image'); - expect(options[0].url).to.equal('https://media.adfrontiers.com/hb/jcm_usersync.png'); - }); - }); -}); diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js new file mode 100644 index 00000000000..ae58da30f64 --- /dev/null +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -0,0 +1,604 @@ +import { expect } from 'chai'; +import { spec, internal as jixieaux, storage } from 'modules/jixieBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; + +describe('jixie Adapter', function () { + const pageurl_ = 'https://testdomain.com/testpage.html'; + const domain_ = 'https://testdomain.com'; + const device_ = 'desktop'; + const timeout_ = 1000; + const currency_ = 'USD'; + + /** + * Basic + */ + 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'); + }); + }); + + /** + * isBidRequestValid + */ + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'jixie', + 'params': { + 'unit': 'prebidsampleunit' + }, + '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 obj does not exist', function () { + let bid0 = Object.assign({}, bid); + delete bid0.params; + expect(spec.isBidRequestValid(bid0)).to.equal(false); + }); + + it('should return false when params obj does not contain unit property', function () { + let bid1 = Object.assign({}, bid); + bid1.params = { rubbish: '' }; + expect(spec.isBidRequestValid(bid1)).to.equal(false); + }); + });// describe + + /** + * buildRequests + */ + describe('buildRequests', function () { + const adUnitCode0_ = 'adunit-code-0'; + const adUnitCode1_ = 'adunit-code-1'; + const adUnitCode2_ = 'adunit-code-2'; + + const bidId0_ = '22a9eb5004cf082'; + const bidId1_ = '230fceb12fd754f'; + const bidId2_ = '24dbe5c4fb80ed8'; + + const bidderRequestId_ = '2131ce076eeaa1b'; + const auctionId_ = '26d68819-d6ce-4a2c-a4d3-f1a97b159d66'; + + const clientIdTest1_ = '1aba6a40-f711-11e9-868c-53a2ae972xxx'; + const sessionIdTest1_ = '1594782644-1aba6a40-f711-11e9-868c-53a2ae972xxx'; + + // to serve as the object that prebid will call jixie buildRequest with: (param2) + const bidderRequest_ = { + refererInfo: {referer: pageurl_}, + auctionId: auctionId_, + timeout: timeout_ + }; + // to serve as the object that prebid will call jixie buildRequest with: (param1) + let bidRequests_ = [ + { + 'bidder': 'jixie', + 'params': { + 'unit': 'prebidsampleunit' + }, + 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': adUnitCode0_, + 'bidId': bidId0_, + 'bidderRequestId': bidderRequestId_, + 'auctionId': auctionId_ + }, + { + 'bidder': 'jixie', + 'params': { + 'unit': 'prebidsampleunit' + }, + 'sizes': [[300, 250]], + 'mediaTypes': { + 'video': { + 'playerSize': [640, 360] + }, + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': adUnitCode1_, + 'bidId': bidId1_, + 'bidderRequestId': bidderRequestId_, + 'auctionId': auctionId_ + }, + { + 'bidder': 'jixie', + 'params': { + 'unit': 'prebidsampleunit' + }, + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'video': { + 'playerSize': [640, 360] + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': adUnitCode2_, + 'bidId': bidId2_, + 'bidderRequestId': bidderRequestId_, + 'auctionId': auctionId_ + } + ]; + + // To serve as a reference to check against the bids array portion of the blob that the call to + // buildRequest returns + const refBids_ = [ + { + 'bidId': bidId0_, + 'adUnitCode': adUnitCode0_, + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'unit': 'prebidsampleunit' + } + }, + { + 'bidId': bidId1_, + 'adUnitCode': adUnitCode1_, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 360] + }, + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'sizes': [[300, 250]], + 'params': { + 'unit': 'prebidsampleunit' + } + }, + { + 'bidId': bidId2_, + 'adUnitCode': adUnitCode2_, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 360] + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'unit': 'prebidsampleunit' + } + } + ]; + + it('should attach valid params to the adserver endpoint (1)', function () { + // this one we do not intercept the cookie stuff so really don't know + // what will be in there. so we do not check here (using expect) + // The next next below we check + const request = spec.buildRequests(bidRequests_, bidderRequest_); + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST') + }) + expect(request.data).to.be.an('string'); + const payload = JSON.parse(request.data); + expect(payload).to.have.property('auctionid', auctionId_); + expect(payload).to.have.property('timeout', timeout_); + expect(payload).to.have.property('currency', currency_); + expect(payload).to.have.property('bids').that.deep.equals(refBids_); + });// it + + it('should attach valid params to the adserver endpoint (2)', function () { + // similar to above test case but here we force some clientid sessionid values + // and domain, pageurl + // get the interceptors ready: + let getCookieStub = sinon.stub(storage, 'getCookie'); + let getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getCookieStub + .withArgs('_jx') + .returns(clientIdTest1_); + getCookieStub + .withArgs('_jxs') + .returns(sessionIdTest1_); + getLocalStorageStub + .withArgs('_jx') + .returns(clientIdTest1_); + getLocalStorageStub + .withArgs('_jxs') + .returns(sessionIdTest1_ + ); + let miscDimsStub = sinon.stub(jixieaux, 'getMiscDims'); + miscDimsStub + .returns({ device: device_, pageurl: pageurl_, domain: domain_ }); + + // actual api call: + const request = spec.buildRequests(bidRequests_, bidderRequest_); + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST') + }) + expect(request.data).to.be.an('string'); + const payload = JSON.parse(request.data); + expect(payload).to.have.property('auctionid', auctionId_); + expect(payload).to.have.property('client_id_c', clientIdTest1_); + expect(payload).to.have.property('client_id_ls', clientIdTest1_); + expect(payload).to.have.property('session_id_c', sessionIdTest1_); + expect(payload).to.have.property('session_id_ls', sessionIdTest1_); + expect(payload).to.have.property('device', device_); + expect(payload).to.have.property('domain', domain_); + expect(payload).to.have.property('pageurl', pageurl_); + expect(payload).to.have.property('timeout', timeout_); + expect(payload).to.have.property('currency', currency_); + expect(payload).to.have.property('bids').that.deep.equals(refBids_); + + // unwire interceptors + getCookieStub.restore(); + getLocalStorageStub.restore(); + miscDimsStub.restore(); + });// it + });// describe + + /** + * interpretResponse: + */ + const JX_OTHER_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.io/dummyscript.js'; + const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.io/jxhboutstream.js'; + + const mockVastXml_ = `JXADSERVERAlway%20Live%20Prebid%20CreativeHybrid in-stream00:00:10`; + const responseBody_ = { + 'bids': [ + // video (vast tag url) returned here + { + 'trackingUrlBase': 'https://tr.jixie.io/sync/ad?', + 'jxBidId': '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', + 'requestId': '62847e4c696edcb', + 'cpm': 2.19, + 'width': 640, + 'height': 360, + 'ttl': 300, + 'adUnitCode': 'demoslot3-div', + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': 'jixie522', + 'meta': { + 'networkId': 123, + 'networkName': 'network123', + 'agencyId': 123, + 'agencyName': 'agency123', + 'advertiserId': 123, + 'advertiserName': 'advertiser123', + 'brandId': 123, + 'brandName': 'brand123', + 'primaryCatId': 1, + 'secondaryCatIds': [ + 2, + 3, + 4 + ], + 'mediaType': 'VIDEO' + }, + 'vastUrl': 'https://ad.jixie.io/v1/video?creativeid=522' + }, + // display ad returned here: This one there is advertiserDomains + // in the response . Will be checked in the unit tests below + { + 'trackingUrlBase': 'https://tr.jixie.io/sync/ad?', + 'jxBidId': '600c9ae6fda1acb-028d5dee-2c83-44e3-bed1-b75002475cdf', + 'requestId': '600c9ae6fda1acb', + 'cpm': 1.999, + 'width': 300, + 'height': 250, + 'ttl': 300, + 'adUnitCode': 'demoslot1-div', + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': 'jixie520', + 'meta': { + 'networkId': 123, + 'networkName': 'network123', + 'agencyId': 123, + 'agencyName': 'agency123', + 'advertiserId': 123, + 'advertiserName': 'advertiser123', + 'advertiserDomains': [ + 'advdom1', + 'advdom2', + 'advdom3' + ], + 'brandId': 123, + 'brandName': 'brand123', + 'primaryCatId': 1, + 'secondaryCatIds': [ + 2, + 3, + 4 + ], + 'mediaType': 'BANNER' + }, + 'ad': '
' + }, + // outstream, jx non-default renderer specified: + { + 'trackingUrlBase': 'https://tr.jixie.io/sync/ad?', + 'jxBidId': '99bc539c81b00ce-028d5dee-2c83-44e3-bed1-b75002475cdf', + 'requestId': '99bc539c81b00ce', + 'cpm': 2.99, + 'width': 640, + 'height': 360, + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': 'jixie521', + 'adUnitCode': 'demoslot4-div', + 'osplayer': 'jixie', + 'osparams': { + 'script': JX_OTHER_OUTSTREAM_RENDERER_URL + }, + 'vastXml': mockVastXml_ + }, + // outstream, jx default renderer: + { + 'trackingUrlBase': 'https://tr.jixie.io/sync/ad?', + 'jxBidId': '61bc539c81b00ce-028d5dee-2c83-44e3-bed1-b75002475cdf', + 'requestId': '61bc539c81b00ce', + 'cpm': 1.99, + 'width': 640, + 'height': 360, + 'ttl': 300, + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': 'jixie521', + 'meta': { + 'networkId': 123, + 'networkName': 'network123', + 'agencyId': 123, + 'agencyName': 'agency123', + 'advertiserId': 123, + 'advertiserName': 'advertiser123', + 'brandId': 123, + 'brandName': 'brand123', + 'primaryCatId': 1, + 'secondaryCatIds': [ + 2, + 3, + 4 + ], + 'mediaType': 'VIDEO' + }, + 'adUnitCode': 'demoslot2-div', + 'osplayer': 'jixie', + 'osparams': {}, + 'vastXml': mockVastXml_ + } + ], + 'setids': { + 'client_id': '43aacc10-f643-11ea-8a10-c5fe2d394e7e', + 'session_id': '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e' + }, + }; + const requestObj_ = + { + 'method': 'POST', + 'url': 'http://localhost:8080/v2/hbpost', + 'data': '{"auctionid":"028d5dee-2c83-44e3-bed1-b75002475cdf","timeout":1000,"currency":"USD","timestamp":1600057934665,"device":"desktop","domain":"mock.com","pageurl":"https://mock.com/tests/jxprebidtest_pbjs.html","bids":[{"bidId":"600c9ae6fda1acb","adUnitCode":"demoslot1-div","mediaTypes":{"banner":{"sizes":[[300,250],[300,600],[728,90]]}},"params":{"unit":"prebidsampleunit"}},{"bidId":"61bc539c81b00ce","adUnitCode":"demoslot2-div","mediaTypes":{"video":{"playerSize":[[640,360]],"context":"outstream"}},"params":{"unit":"prebidsampleunit"}},{"bidId":"99bc539c81b00ce","adUnitCode":"demoslot4-div","mediaTypes":{"video":{"playerSize":[[640,360]],"context":"outstream"}},"params":{"unit":"prebidsampleunit"}},{"bidId":"62847e4c696edcb","adUnitCode":"demoslot3-div","mediaTypes":{"video":{"playerSize":[[640,360]],"context":"instream"}},"params":{"unit":"prebidsampleunit"}},{"bidId":"6360235ab01d2cd","adUnitCode":"woo-div","mediaTypes":{"video":{"context":"outstream","playerSize":[[640,360]]}},"params":{"unit":"80b76fc951e161d7c019d21b6639e408"}},{"bidId":"64d9724c7a5e512","adUnitCode":"test-div","mediaTypes":{"video":{"context":"outstream","playerSize":[[300,250]]}},"params":{"unit":"80b76fc951e161d7c019d21b6639e408"}},{"bidId":"65bea7e80fed44b","adUnitCode":"test-div","mediaTypes":{"banner":{"sizes":[[300,250],[300,600],[728,90]]}},"params":{"unit":"7854f723e932b951b6c51fc80b23a410"}},{"bidId":"6642054c4ba1b7f","adUnitCode":"div-banner-native-1","mediaTypes":{"banner":{"sizes":[[640,360]]},"video":{"context":"outstream","sizes":[[640,361]],"playerSize":[[640,360]]},"native":{"type":"image"}},"params":{"unit":"632e7695f0910ce0fa74c19859060a04"}},{"bidId":"675ecf4b44db228","adUnitCode":"div-banner-native-2","mediaTypes":{"banner":{"sizes":[[300,250]]},"native":{"title":{"required":true},"image":{"required":true},"sponsoredBy":{"required":true}}},"params":{"unit":"1000008-b1Q2UMQfZx"}},{"bidId":"68f2dbf5dc23f94","adUnitCode":"div-Top-MediumRectangle","mediaTypes":{"banner":{"sizes":[[300,250],[300,100],[320,50]]}},"params":{"unit":"1000008-b1Q2UMQfZx"}},{"bidId":"6991cf107bb7f1a","adUnitCode":"div-Middle-MediumRectangle","mediaTypes":{"banner":{"sizes":[[300,250],[300,100],[320,50]]}},"params":{"unit":"1000008-b1Q2UMQfZx"}},{"bidId":"706be1b011eac83","adUnitCode":"div-Inside-MediumRectangle","mediaTypes":{"banner":{"sizes":[[300,600],[300,250],[300,100],[320,480]]}},"params":{"unit":"1000008-b1Q2UMQfZx"}}],"client_id_c":"ebd0dea0-f5c8-11ea-a2c7-a5b37aa7fe95","client_id_ls":"ebd0dea0-f5c8-11ea-a2c7-a5b37aa7fe95","session_id_c":"","session_id_ls":"1600005388-ebd0dea0-f5c8-11ea-a2c7-a5b37aa7fe95"}', + 'currency': 'USD' + }; + + describe('interpretResponse', function () { + it('handles nobid responses', function () { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }); + + it('should get correct bid response', function () { + let setCookieSpy = sinon.spy(storage, 'setCookie'); + let setLocalStorageSpy = sinon.spy(storage, 'setDataInLocalStorage'); + const result = spec.interpretResponse({body: responseBody_}, requestObj_) + expect(setLocalStorageSpy.calledWith('_jx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setLocalStorageSpy.calledWith('_jxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setCookieSpy.calledWith('_jxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setCookieSpy.calledWith('_jx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + + // video ad with vastUrl returned by adserver + expect(result[0].requestId).to.equal('62847e4c696edcb') + expect(result[0].cpm).to.equal(2.19) + expect(result[0].width).to.equal(640) + expect(result[0].height).to.equal(360) + expect(result[0].creativeId).to.equal('jixie522') + expect(result[0].currency).to.equal('USD') + expect(result[0].netRevenue).to.equal(true) + expect(result[0].ttl).to.equal(300) + expect(result[0].vastUrl).to.include('https://ad.jixie.io/v1/video?creativeid=') + expect(result[0].trackingUrlBase).to.include('sync') + // We will always make sure the meta->advertiserDomains property is there + // If no info it is an empty array. + expect(result[0].meta.advertiserDomains.length).to.equal(0) + + // display ad + expect(result[1].requestId).to.equal('600c9ae6fda1acb') + expect(result[1].cpm).to.equal(1.999) + expect(result[1].width).to.equal(300) + expect(result[1].height).to.equal(250) + expect(result[1].creativeId).to.equal('jixie520') + expect(result[1].currency).to.equal('USD') + expect(result[1].netRevenue).to.equal(true) + expect(result[1].ttl).to.equal(300) + expect(result[1].ad).to.include('jxoutstream') + expect(result[1].meta.advertiserDomains.length).to.equal(3) + expect(result[1].trackingUrlBase).to.include('sync') + + // should pick up about using alternative outstream renderer + expect(result[2].requestId).to.equal('99bc539c81b00ce') + expect(result[2].cpm).to.equal(2.99) + expect(result[2].width).to.equal(640) + expect(result[2].height).to.equal(360) + expect(result[2].creativeId).to.equal('jixie521') + expect(result[2].currency).to.equal('USD') + expect(result[2].netRevenue).to.equal(true) + expect(result[2].ttl).to.equal(300) + expect(result[2].vastXml).to.include('') + expect(result[2].trackingUrlBase).to.include('sync'); + expect(result[2].renderer.id).to.equal('demoslot4-div') + expect(result[2].meta.advertiserDomains.length).to.equal(0) + expect(result[2].renderer.url).to.equal(JX_OTHER_OUTSTREAM_RENDERER_URL); + + // should know to use default outstream renderer + expect(result[3].requestId).to.equal('61bc539c81b00ce') + expect(result[3].cpm).to.equal(1.99) + expect(result[3].width).to.equal(640) + expect(result[3].height).to.equal(360) + expect(result[3].creativeId).to.equal('jixie521') + expect(result[3].currency).to.equal('USD') + expect(result[3].netRevenue).to.equal(true) + expect(result[3].ttl).to.equal(300) + expect(result[3].vastXml).to.include('') + expect(result[3].trackingUrlBase).to.include('sync'); + expect(result[3].renderer.id).to.equal('demoslot2-div') + expect(result[3].meta.advertiserDomains.length).to.equal(0) + expect(result[3].renderer.url).to.equal(JX_OUTSTREAM_RENDERER_URL) + + setLocalStorageSpy.restore(); + setCookieSpy.restore(); + });// it + });// describe + + /** + * onBidWon + */ + describe('onBidWon', function() { + let ajaxStub; + let miscDimsStub; + beforeEach(function() { + miscDimsStub = sinon.stub(jixieaux, 'getMiscDims'); + ajaxStub = sinon.stub(jixieaux, 'ajax'); + + miscDimsStub + .returns({ device: device_, pageurl: pageurl_, domain: domain_ }); + }) + + afterEach(function() { + miscDimsStub.restore(); + ajaxStub.restore(); + }) + + let TRACKINGURL_ = 'https://abc.com/sync?action=bidwon'; + + it('Should fire if the adserver trackingUrl flag says so', function() { + spec.onBidWon({ trackingUrl: TRACKINGURL_ }) + expect(jixieaux.ajax.calledWith(TRACKINGURL_)).to.equal(true); + }) + + it('Should not fire if the adserver response indicates no firing', function() { + let called = false; + ajaxStub.callsFake(function fakeFn() { + called = true; + }); + spec.onBidWon({ notrack: 1 }) + expect(called).to.equal(false); + }); + + // A reference to check again: + const QPARAMS_ = { + action: 'hbbidwon', + device: device_, + pageurl: encodeURIComponent(pageurl_), + domain: encodeURIComponent(domain_), + cid: 121, + cpid: 99, + jxbidid: '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', + auctionid: '028d5dee-2c83-44e3-bed1-b75002475cdf', + cpm: 1.11, + requestid: '62847e4c696edcb' + }; + + it('check it is sending the correct ajax url and qparameters', function() { + spec.onBidWon({ + trackingUrlBase: 'https://mytracker.com/sync?', + cid: 121, + cpid: 99, + jxBidId: '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', + auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', + cpm: 1.11, + requestId: '62847e4c696edcb' + }) + expect(jixieaux.ajax.calledWith('https://mytracker.com/sync?', null, QPARAMS_)).to.equal(true); + }); + }); // describe + + /** + * onTimeout + */ + describe('onTimeout', function() { + let ajaxStub; + let miscDimsStub; + beforeEach(function() { + ajaxStub = sinon.stub(jixieaux, 'ajax'); + miscDimsStub = sinon.stub(jixieaux, 'getMiscDims'); + miscDimsStub + .returns({ device: device_, pageurl: pageurl_, domain: domain_ }); + }) + + afterEach(function() { + miscDimsStub.restore(); + ajaxStub.restore(); + }) + + // reference to check against: + const QPARAMS_ = { + action: 'hbtimeout', + device: device_, + pageurl: encodeURIComponent(pageurl_), + domain: encodeURIComponent(domain_), + auctionid: '028d5dee-2c83-44e3-bed1-b75002475cdf', + timeout: 1000, + count: 2 + }; + + it('check it is sending the correct ajax url and qparameters', function() { + spec.onTimeout([ + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} + ]) + expect(jixieaux.ajax.calledWith(spec.EVENTS_URL, null, QPARAMS_)).to.equal(true); + }) + + it('if turned off via config then dont do onTimeout sending of event', function() { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'jixie') { + return { onTimeout: 'off' }; + } + return null; + }); + let called = false; + ajaxStub.callsFake(function fakeFn() { + called = true; + }); + spec.onTimeout([ + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} + ]) + expect(called).to.equal(false); + getConfigStub.restore(); + }) + + const otherUrl_ = 'https://other.azurewebsites.net/sync/evt?'; + it('if config specifies a different endpoint then should send there instead', function() { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'jixie') { + return { onTimeoutUrl: otherUrl_ }; + } + return null; + }); + spec.onTimeout([ + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, + {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} + ]) + expect(jixieaux.ajax.calledWith(otherUrl_, null, QPARAMS_)).to.equal(true); + getConfigStub.restore(); + }) + });// describe +}); diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index c162b785aed..cb3648cba44 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -21,7 +21,9 @@ describe('justpremium adapter', function () { }, userId: { tdid: '1111111', - id5id: '2222222', + id5id: { + uid: '2222222' + }, digitrustid: { data: { id: '3333333' @@ -84,7 +86,7 @@ describe('justpremium adapter', function () { expect(jpxRequest.version.jp_adapter).to.equal('1.7') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') - expect(jpxRequest.uids.id5id).to.equal('2222222') + expect(jpxRequest.uids.id5id.uid).to.equal('2222222') expect(jpxRequest.uids.digitrustid.data.id).to.equal('3333333') expect(jpxRequest.us_privacy).to.equal('1YYN') }) @@ -101,7 +103,8 @@ describe('justpremium adapter', function () { 'width': 970, 'price': 0.52, 'format': 'lb', - 'adm': 'creative code' + 'adm': 'creative code', + 'adomain': ['justpremium.com'] }] }, 'pass': { @@ -121,7 +124,10 @@ describe('justpremium adapter', function () { netRevenue: true, currency: 'USD', ttl: 60000, - format: 'lb' + format: 'lb', + meta: { + advertiserDomains: ['justpremium.com'] + }, } ] @@ -138,6 +144,7 @@ describe('justpremium adapter', function () { expect(result[0].creativeId).to.equal(3213123) expect(result[0].netRevenue).to.equal(true) expect(result[0].format).to.equal('lb') + expect(result[0].meta.advertiserDomains[0]).to.equal('justpremium.com') }) it('Verify wrong server response', function () { diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js new file mode 100644 index 00000000000..458e45e8ae7 --- /dev/null +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -0,0 +1,751 @@ +import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, + formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, + fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('jwplayerRtdProvider', function() { + const testIdForSuccess = 'test_id_for_success'; + const testIdForFailure = 'test_id_for_failure'; + const validSegments = ['test_seg_1', 'test_seg_2']; + const responseHeader = {'Content-Type': 'application/json'}; + + describe('Fetch targeting for mediaID tests', function () { + let request; + + describe('Fetch succeeds', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForSuccess); + request = server.requests[0]; + }); + + afterEach(function () { + server.respond(); + }); + + it('should reach out to media endpoint', function () { + expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testIdForSuccess}`); + }); + + it('should write to cache when successful', function () { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments + } + ] + }) + ); + + const targetingInfo = getVatFromCache(testIdForSuccess); + + const validTargeting = { + segments: validSegments, + mediaID: testIdForSuccess + }; + + expect(targetingInfo).to.deep.equal(validTargeting); + }); + }); + + describe('Fetch fails', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForFailure); + request = server.requests[0] + }); + + it('should not write to cache when response is malformed', function() { + request.respond('{]'); + const targetingInfo = getVatFromCache(testIdForFailure); + expect(targetingInfo).to.be.null; + }); + + it('should not write to cache when playlist is absent', function() { + request.respond({}); + const targetingInfo = getVatFromCache(testIdForFailure); + expect(targetingInfo).to.be.null; + }); + + it('should not write to cache when segments are absent', function() { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4' + } + ] + }) + ); + const targetingInfo = getVatFromCache(testIdForFailure); + expect(targetingInfo).to.be.null; + }); + + it('should not write to cache when request errors', function() { + request.error(); + const targetingInfo = getVatFromCache(testIdForFailure); + expect(targetingInfo).to.be.null; + }); + }); + }); + + describe('Format targeting response', function () { + it('should exclude segment key when absent', function () { + const targeting = formatTargetingResponse({ mediaID: 'test' }); + expect(targeting).to.not.have.property('segments'); + }); + + it('should exclude content block when mediaId is absent', function () { + const targeting = formatTargetingResponse({ segments: ['test'] }); + expect(targeting).to.not.have.property('content'); + }); + + it('should return proper format', function () { + const segments = ['123']; + const mediaID = 'test'; + const expectedContentId = 'jw_' + mediaID; + const expectedContent = { + id: expectedContentId + }; + const targeting = formatTargetingResponse({ + segments, + mediaID + }); + expect(targeting).to.have.deep.property('segments', segments); + expect(targeting).to.have.deep.property('content', expectedContent); + }); + }); + + describe('Get VAT from player', function () { + const mediaIdWithSegment = 'media_ID_1'; + const mediaIdNoSegment = 'media_ID_2'; + const mediaIdForCurrentItem = 'media_ID_current'; + const mediaIdNotCached = 'media_test_ID'; + + const validPlayerID = 'player_test_ID_valid'; + const invalidPlayerID = 'player_test_ID_invalid'; + + it('returns null when jwplayer.js is absent from page', function () { + const targeting = getVatFromPlayer(invalidPlayerID, mediaIdNotCached); + expect(targeting).to.be.null; + }); + + describe('When jwplayer.js is on page', function () { + const playlistItemWithSegmentMock = { + mediaid: mediaIdWithSegment, + jwpseg: validSegments + }; + + const targetingForMediaWithSegment = { + segments: validSegments, + mediaID: mediaIdWithSegment + }; + + const playlistItemNoSegmentMock = { + mediaid: mediaIdNoSegment + }; + + const currentItemSegments = ['test_seg_3', 'test_seg_4']; + const currentPlaylistItemMock = { + mediaid: mediaIdForCurrentItem, + jwpseg: currentItemSegments + }; + const targetingForCurrentItem = { + segments: currentItemSegments, + mediaID: mediaIdForCurrentItem + }; + + const playerInstanceMock = { + getPlaylist: function () { + return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; + }, + + getPlaylistItem: function () { + return currentPlaylistItemMock; + } + }; + + const jwplayerMock = function(playerID) { + if (playerID === validPlayerID) { + return playerInstanceMock; + } else { + return {}; + } + }; + + beforeEach(function () { + window.jwplayer = jwplayerMock; + }); + + it('returns null when player ID does not match player on page', function () { + const targeting = getVatFromPlayer(invalidPlayerID, mediaIdNotCached); + expect(targeting).to.be.null; + }); + + it('returns segments when media ID matches a playlist item with segments', function () { + const targeting = getVatFromPlayer(validPlayerID, mediaIdWithSegment); + expect(targeting).to.deep.equal(targetingForMediaWithSegment); + }); + + it('caches segments when media ID matches a playist item with segments', function () { + getVatFromPlayer(validPlayerID, mediaIdWithSegment); + const vat = getVatFromCache(mediaIdWithSegment); + expect(vat.segments).to.deep.equal(validSegments); + }); + + it('returns segments of current item when media ID is missing', function () { + const targeting = getVatFromPlayer(validPlayerID); + expect(targeting).to.deep.equal(targetingForCurrentItem); + }); + + it('caches segments from the current item', function () { + getVatFromPlayer(validPlayerID); + + window.jwplayer = null; + const targeting = getVatFromCache(mediaIdForCurrentItem); + expect(targeting).to.deep.equal(targetingForCurrentItem); + }); + + it('returns undefined segments when segments are absent', function () { + const targeting = getVatFromPlayer(validPlayerID, mediaIdNoSegment); + expect(targeting).to.deep.equal({ + mediaID: mediaIdNoSegment, + segments: undefined + }); + }); + + describe('Get Bid Request Data', function () { + it('executes immediately while request is active if player has item', function () { + const bidRequestSpy = sinon.spy(); + const fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; + + fetchTargetingForMediaId(mediaIdWithSegment); + + const bid = {}; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: mediaIdWithSegment, + playerID: validPlayerID + } + } + } + }, + bids: [ + bid + ] + }; + const expectedContentId = 'jw_' + mediaIdWithSegment; + const expectedTargeting = { + segments: validSegments, + content: { + id: expectedContentId + } + }; + jwplayerSubmodule.getBidRequestData({ adUnits: [adUnit] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); + fakeServer.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + }); + }); + }); + + describe('Enrich ad units', function () { + const contentIdForSuccess = 'jw_' + testIdForSuccess; + const expectedTargetingForSuccess = { + segments: validSegments, + content: { + id: contentIdForSuccess + } + }; + let bidRequestSpy; + let fakeServer; + let clock; + + beforeEach(function () { + bidRequestSpy = sinon.spy(); + + fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; + + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + fakeServer.respond(); + }); + + it('adds targeting when pending request succeeds', function () { + fetchTargetingForMediaId(testIdForSuccess); + const bids = [ + { + id: 'bid1' + }, + { + id: 'bid2' + } + ]; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } + }, + bids + }; + + enrichAdUnits([adUnit]); + const bid1 = bids[0]; + const bid2 = bids[1]; + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); + + const request = fakeServer.requests[0]; + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments + } + ] + }) + ); + + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + }); + + it('immediately adds cached targeting', function () { + fetchTargetingForMediaId(testIdForSuccess); + const bids = [ + { + id: 'bid1' + }, + { + id: 'bid2' + } + ]; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } + }, + bids + }; + const request = fakeServer.requests[0]; + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments + } + ] + }) + ); + + enrichAdUnits([adUnit]); + const bid1 = bids[0]; + const bid2 = bids[1]; + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + }); + + it('adds content block when segments are absent and no request is pending', function () { + const expectedTargetingForFailure = { + content: { + id: 'jw_' + testIdForFailure + } + }; + const bids = [ + { + id: 'bid1' + }, + { + id: 'bid2' + } + ]; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: testIdForFailure + } + } + } + }, + bids + }; + + enrichAdUnits([adUnit]); + const bid1 = bids[0]; + const bid2 = bids[1]; + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); + }); + }); + + describe(' Extract Publisher Params', function () { + const config = { mediaID: 'test' }; + + it('should exclude adUnits that do not support instream video and do not specify jwTargeting', function () { + const oustreamAdUnit = { mediaTypes: { video: { context: 'outstream' } } }; + const oustreamTargeting = extractPublisherParams(oustreamAdUnit, config); + expect(oustreamTargeting).to.be.undefined; + + const bannerAdUnit = { mediaTypes: { banner: {} } }; + const bannerTargeting = extractPublisherParams(bannerAdUnit, config); + expect(bannerTargeting).to.be.undefined; + + const targeting = extractPublisherParams({}, config); + expect(targeting).to.be.undefined; + }); + + it('should include ad unit when media type is video and is instream', function () { + const adUnit = { mediaTypes: { video: { context: 'instream' } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should include banner ad units that specify jwTargeting', function() { + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should include outstream ad units that specify jwTargeting', function() { + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should fallback to config when empty jwTargeting is defined in ad unit', function () { + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should prioritize adUnit properties ', function () { + const expectedMediaID = 'test_media_id'; + const expectedPlayerID = 'test_player_id'; + const config = { playerID: 'bad_id', mediaID: 'bad_id' }; + + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.have.property('mediaID', expectedMediaID); + expect(targeting).to.have.property('playerID', expectedPlayerID); + }); + + it('should use config properties as fallbacks', function () { + const expectedMediaID = 'test_media_id'; + const expectedPlayerID = 'test_player_id'; + const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; + + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.have.property('mediaID', expectedMediaID); + expect(targeting).to.have.property('playerID', expectedPlayerID); + }); + + it('should return undefined when Publisher Params are absent', function () { + const targeting = extractPublisherParams({}, null); + expect(targeting).to.be.undefined; + }) + }); + + describe('Add Targeting to Bid', function () { + const targeting = {foo: 'bar'}; + + it('creates realTimeData when absent from Bid', function () { + const targeting = {foo: 'bar'}; + const bid = {}; + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + expect(bid).to.have.nested.property('rtd.jwplayer.targeting', targeting); + }); + + it('adds to existing realTimeData', function () { + const otherRtd = { + targeting: { + seg: 'rtd seg' + } + }; + + const bid = { + rtd: { + otherRtd + } + }; + + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + + expect(rtd).to.have.deep.property('otherRtd', otherRtd); + }); + + it('adds to existing realTimeData.jwplayer', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + otherInfo + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.otherInfo', otherInfo); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('overrides existing jwplayer.targeting', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + targeting: { + otherInfo + } + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('creates jwplayer when absent from realTimeData', function () { + const bid = { rtd: {} }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + }); + + describe('jwplayerSubmodule', function () { + it('successfully instantiates', function () { + expect(jwplayerSubmodule.init()).to.equal(true); + }); + + describe('Get Bid Request Data', function () { + const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; + let bidRequestSpy; + let fakeServer; + let clock; + let bidReqConfig; + + beforeEach(function () { + bidReqConfig = { + adUnits: [ + { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: validMediaIDs[0] + } + } + } + }, + bids: [ + {}, {} + ] + }, + { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: validMediaIDs[1] + } + } + } + }, + bids: [ + {}, {} + ] + } + ] + }; + + bidRequestSpy = sinon.spy(); + + fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; + + clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + fakeServer.respond(); + }); + + it('executes callback immediately when ad units are missing', function () { + jwplayerSubmodule.getBidRequestData({ adUnits: [] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('executes callback immediately when no requests are pending', function () { + fetchTargetingInformation({ + mediaIDs: [] + }); + jwplayerSubmodule.getBidRequestData(bidReqConfig, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('executes callback only after requests in adUnit complete', function() { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + jwplayerSubmodule.getBidRequestData(bidReqConfig, bidRequestSpy); + expect(bidRequestSpy.notCalled).to.be.true; + + const req1 = fakeServer.requests[0]; + const req2 = fakeServer.requests[1]; + const req3 = fakeServer.requests[2]; + + req1.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req2.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + + req3.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('sets targeting data in proper structure', function () { + const bid = {}; + const adUnitWithMediaId = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } + }, + bids: [ + bid + ] + }; + const adUnitEmpty = { + code: 'test_ad_unit_empty' + }; + const expectedContentId = 'jw_' + testIdForSuccess; + const expectedTargeting = { + segments: validSegments, + content: { + id: expectedContentId + } + }; + jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); + }); + + it('excludes segments when absent', function () { + const adUnitCode = 'test_ad_unit'; + const bid = {}; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: testIdForFailure + } + } + } + }, + bids: [ bid ] + }; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; + const expectedTargeting = { + content: { + id: expectedContentId + } + }; + + jwplayerSubmodule.getBidRequestData({ adUnits: [ adUnit ] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); + }); + + it('does not modify bid when jwTargeting block is absent', function () { + const adUnitCode = 'test_ad_unit'; + const bid1 = {}; + const bid2 = {}; + const bid3 = {}; + const adUnitWithMediaId = { + code: adUnitCode, + mediaID: testIdForSuccess, + bids: [ bid1 ] + }; + const adUnitEmpty = { + code: 'test_ad_unit_empty', + bids: [ bid2 ] + }; + + const adUnitEmptyfpd = { + code: 'test_ad_unit_empty_fpd', + ortb2Imp: { + ext: { + id: 'sthg' + } + }, + bids: [ bid3 ] + }; + + jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty, adUnitEmptyfpd] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); + expect(bid3).to.not.have.property('rtd'); + }); + }); + }); +}); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index be73e140839..43968bbef5a 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -134,6 +134,21 @@ describe('kargo adapter tests', function () { noAdServerCurrency = true; } + function generateGDPR(applies, haveConsent) { + var data = { + consentString: 'gdprconsentstring', + gdprApplies: applies, + }; + return data; + } + + function generateGDPRExpect(applies, haveConsent) { + return { + consent: 'gdprconsentstring', + applies: applies, + }; + } + function initializeKruxUser() { setLocalStorageItem('kxkar_user', 'rsgr9pnij'); } @@ -221,7 +236,7 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) { + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { var base = { timeout: 200, requestCount: requestCount++, @@ -299,6 +314,10 @@ describe('kargo adapter tests', function () { rawCRBLocalStorage: expectedRawCRB }; + if (expectedGDPR) { + base.userIDs['gdpr'] = expectedGDPR; + } + if (excludeUserIds === true) { base.userIDs = { crbIDs: {}, @@ -317,12 +336,16 @@ describe('kargo adapter tests', function () { return base; } - function testBuildRequests(excludeTdid, expected) { + function testBuildRequests(excludeTdid, expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var request = spec.buildRequests(clonedBids, {timeout: 200, uspConsent: '1---', foo: 'bar'}); + var payload = { timeout: 200, uspConsent: '1---', foo: 'bar' }; + if (gdpr) { + payload['gdprConsent'] = gdpr + } + var request = spec.buildRequests(clonedBids, payload); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); @@ -431,6 +454,15 @@ describe('kargo adapter tests', function () { initializeKrgCrb(); testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); }); + + it('sends gdpr consent', function () { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + }); }); describe('response handler', function() { @@ -505,7 +537,8 @@ describe('kargo adapter tests', function () { netRevenue: true, currency: 'USD', meta: { - clickUrl: 'https://foobar.com' + clickUrl: 'https://foobar.com', + advertiserDomains: ['https://foobar.com'] } }, { requestId: '3', @@ -557,8 +590,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed() { - return spec.getUserSyncs({iframeEnabled: true}); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); } function getUserSyncsWhenForbidden() { @@ -573,17 +606,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` }; } - function getSyncUrls() { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); } return syncs; } @@ -605,6 +638,21 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); }); + it('no user syncs when there is no us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + }); + + it('pass through us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + }); + + it('pass through gdpr consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js new file mode 100644 index 00000000000..76c2c287989 --- /dev/null +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -0,0 +1,705 @@ +import {expect} from 'chai'; +import {spec} from 'modules/koblerBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {getRefererInfo} from 'src/refererDetection.js'; + +function createBidderRequest(auctionId, timeout, pageUrl) { + return { + auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', + timeout: timeout || 2000, + refererInfo: { + referer: pageUrl || 'example.com' + } + }; +} + +function createValidBidRequest(params, bidId, sizes) { + return { + adUnitCode: 'adunit-code', + bidId: bidId || '22c4871113f461', + bidder: 'kobler', + bidderRequestId: '15246a574e859f', + bidRequestsCount: 1, + bidderRequestsCount: 1, + mediaTypes: { + banner: { + sizes: sizes || [[300, 250], [320, 100]] + } + }, + params: params || { + placementId: 'tpw58278' + }, + transactionTd: '04314114-15bd-4638-8664-bdb8bdc60bff' + }; +} + +describe('KoblerAdapter', function () { + 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 not accept a request without bidId as valid', function () { + const bid = { + params: { + someParam: 'abc' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without params as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589' + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without placementId as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + params: { + someParam: 'abc' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should accept a request with bidId and placementId as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + params: { + someParam: 'abc', + placementId: '8bde0923-1409-4253-9594-495b58d931ba' + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + }); + + describe('buildRequests', function () { + it('should read data from bidder request', function () { + const testUrl = 'kobler.no'; + const auctionId = '8319af54-9795-4642-ba3a-6f57d6ff9100'; + const timeout = 5000; + const validBidRequests = [createValidBidRequest()]; + const bidderRequest = createBidderRequest(auctionId, timeout, testUrl); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.tmax).to.be.equal(timeout); + expect(openRtbRequest.id).to.be.equal(auctionId); + expect(openRtbRequest.site.page).to.be.equal(testUrl); + }); + + it('should read data from valid bid requests', function () { + const firstSize = [400, 800]; + const secondSize = [450, 950]; + const sizes = [firstSize, secondSize]; + const placementId = 'tsjs86325'; + const bidId = '3a56a019-4835-4f75-811c-76fac6853a2c'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: placementId + }, + bidId, + sizes + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].id).to.be.equal(bidId); + expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[0].banner.w).to.be.equal(firstSize[0]); + expect(openRtbRequest.imp[0].banner.h).to.be.equal(firstSize[1]); + expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(2); + expect(openRtbRequest.imp[0].banner.format[0].w).to.be.equal(firstSize[0]); + expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(firstSize[1]); + expect(openRtbRequest.imp[0].banner.format[1].w).to.be.equal(secondSize[0]); + expect(openRtbRequest.imp[0].banner.format[1].h).to.be.equal(secondSize[1]); + }); + + it('should use 0x0 as default size', function () { + const validBidRequests = [ + createValidBidRequest( + undefined, + undefined, + [] + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.w).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.h).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.format[0].w).to.be.equal(0); + expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(0); + }); + + it('should use 0 as default position', function () { + const validBidRequests = [createValidBidRequest()]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(0); + }); + + it('should read zip from valid bid requests', function () { + const zip = '700 02'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'nmah8324234', + zip: zip + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.device.geo.zip).to.be.equal(zip); + }); + + it('should read test from valid bid requests', function () { + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'zwop842799', + test: true + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.test).to.be.equal(1); + }); + + it('should read floorPrice from valid bid requests', function () { + const floorPrice = 4.343; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'oqr3224234', + floorPrice: floorPrice + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); + }); + + it('should read position from valid bid requests', function () { + const placementId = 'yzksf234592'; + const validBidRequests = [ + createValidBidRequest( + { + placementId: placementId, + position: 1 + } + ), + createValidBidRequest( + { + placementId: placementId, + position: 2 + } + ), + createValidBidRequest( + { + placementId: placementId, + position: 3 + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(3); + expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(1); + expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[1].banner.ext.kobler.pos).to.be.equal(2); + expect(openRtbRequest.imp[1].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp[2].banner.ext.kobler.pos).to.be.equal(3); + expect(openRtbRequest.imp[2].tagid).to.be.equal(placementId); + }); + + it('should read dealIds from valid bid requests', function () { + const dealIds1 = ['78214682234823']; + const dealIds2 = ['89913861235234', '27368423545328640']; + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'rsl1239823', + dealIds: dealIds1 + } + ), + createValidBidRequest( + { + placementId: 'pqw234232', + dealIds: dealIds2 + } + ) + ]; + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(2); + expect(openRtbRequest.imp[0].pmp.deals.length).to.be.equal(1); + expect(openRtbRequest.imp[0].pmp.deals[0].id).to.be.equal(dealIds1[0]); + expect(openRtbRequest.imp[1].pmp.deals.length).to.be.equal(2); + expect(openRtbRequest.imp[1].pmp.deals[0].id).to.be.equal(dealIds2[0]); + expect(openRtbRequest.imp[1].pmp.deals[1].id).to.be.equal(dealIds2[1]); + }); + + it('should read timeout from config', function () { + const timeout = 4000; + const validBidRequests = [createValidBidRequest()]; + // No timeout field + const bidderRequest = { + auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', + refererInfo: { + referer: 'example.com' + } + }; + config.setConfig({ + bidderTimeout: timeout + }); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.tmax).to.be.equal(timeout); + }); + + it('should read floor price using floors module', function () { + const floorPriceFor580x400 = 6.5148; + const floorPriceForAnySize = 4.2343; + const validBidRequests = [ + createValidBidRequest(undefined, '98efe127-f926-4dde-b988-db8e5dba5a76', [[580, 400]]), + createValidBidRequest(undefined, 'c7698d4a-94f4-4a6b-a928-7e1facfbf752', []) + ]; + validBidRequests.forEach(validBidRequest => { + validBidRequest.getFloor = function (params) { + let floorPrice; + if (utils.isArray(params.size) && params.size[0] === 580 && params.size[1] === 400) { + floorPrice = floorPriceFor580x400; + } else if (params.size === '*') { + floorPrice = floorPriceForAnySize + } else { + floorPrice = 0 + } + return { + currency: params.currency, + floor: floorPrice + } + } + }) + const bidderRequest = createBidderRequest(); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + expect(openRtbRequest.imp.length).to.be.equal(2); + expect(openRtbRequest.imp[0].id).to.be.equal('98efe127-f926-4dde-b988-db8e5dba5a76'); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPriceFor580x400); + expect(openRtbRequest.imp[1].id).to.be.equal('c7698d4a-94f4-4a6b-a928-7e1facfbf752'); + expect(openRtbRequest.imp[1].bidfloor).to.be.equal(floorPriceForAnySize); + }); + + it('should create whole OpenRTB request', function () { + const validBidRequests = [ + createValidBidRequest( + { + placementId: 'pcha322364', + zip: '0015', + floorPrice: 5.6234, + position: 1, + dealIds: ['623472534328234'] + }, + '953ee65d-d18a-484f-a840-d3056185a060', + [[400, 600]] + ), + createValidBidRequest( + { + placementId: 'sdfgoi32y4', + floorPrice: 3.2543, + position: 2, + dealIds: ['92368234753283', '263845832942'] + }, + '8320bf79-9d90-4a17-87c6-5d505706a921', + [[400, 500], [200, 250], [300, 350]] + ), + createValidBidRequest( + { + placementId: 'gwms2738647', + position: 3 + }, + 'd0de713b-32e3-4191-a2df-a007f08ffe72', + [[800, 900]] + ) + ]; + const bidderRequest = createBidderRequest( + '9ff580cf-e10e-4b66-add7-40ac0c804e21', + 4500, + 'bid.kobler.no' + ); + + const result = spec.buildRequests(validBidRequests, bidderRequest); + const openRtbRequest = JSON.parse(result.data); + + const expectedOpenRtbRequest = { + id: '9ff580cf-e10e-4b66-add7-40ac0c804e21', + at: 1, + tmax: 4500, + cur: ['USD'], + imp: [ + { + id: '953ee65d-d18a-484f-a840-d3056185a060', + banner: { + format: [ + { + w: 400, + h: 600 + } + ], + w: 400, + h: 600, + ext: { + kobler: { + pos: 1 + } + } + }, + tagid: 'pcha322364', + bidfloor: 5.6234, + bidfloorcur: 'USD', + pmp: { + deals: [ + { + id: '623472534328234' + } + ] + } + }, + { + id: '8320bf79-9d90-4a17-87c6-5d505706a921', + banner: { + format: [ + { + w: 400, + h: 500 + }, + { + w: 200, + h: 250 + }, + { + w: 300, + h: 350 + } + ], + w: 400, + h: 500, + ext: { + kobler: { + pos: 2 + } + } + }, + tagid: 'sdfgoi32y4', + bidfloor: 3.2543, + bidfloorcur: 'USD', + pmp: { + deals: [ + { + id: '92368234753283' + }, + { + id: '263845832942' + } + ] + } + }, + { + id: 'd0de713b-32e3-4191-a2df-a007f08ffe72', + banner: { + format: [ + { + w: 800, + h: 900 + } + ], + w: 800, + h: 900, + ext: { + kobler: { + pos: 3 + } + } + }, + tagid: 'gwms2738647', + bidfloor: 0, + bidfloorcur: 'USD', + pmp: {} + } + ], + device: { + devicetype: 2, + geo: { + zip: '0015' + } + }, + site: { + page: 'bid.kobler.no' + }, + test: 0 + }; + + expect(openRtbRequest).to.deep.equal(expectedOpenRtbRequest); + }); + }); + + describe('interpretResponse', function () { + it('should handle empty body', function () { + const responseWithEmptyBody = { + body: undefined + }; + const bids = spec.interpretResponse(responseWithEmptyBody) + + expect(bids.length).to.be.equal(0); + }); + + it('should generate bids from OpenRTB response', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'NOK' + } + }); + const responseWithTwoBids = { + body: { + seatbid: [ + { + bid: [ + { + impid: '6194ddef-89a4-404f-9efd-6b718fc23308', + price: 7.981, + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + crid: 'edea9b03-3a57-41aa-9c00-abd673e22006', + dealid: '', + w: 320, + h: 250, + adm: '', + adomain: [ + 'https://kobler.no' + ] + }, + { + impid: '2ec0b40f-d3ca-4ba5-8ce3-48290565690f', + price: 6.71234, + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + crid: 'fa2d5af7-2678-4204-9023-44c526160742', + dealid: '2783483223432342', + w: 580, + h: 400, + adm: '', + adomain: [ + 'https://bid.kobler.no' + ] + } + ] + } + ], + cur: 'USD' + } + }; + const bids = spec.interpretResponse(responseWithTwoBids) + + const expectedBids = [ + { + requestId: '6194ddef-89a4-404f-9efd-6b718fc23308', + cpm: 7.981, + currency: 'USD', + width: 320, + height: 250, + creativeId: 'edea9b03-3a57-41aa-9c00-abd673e22006', + dealId: '', + netRevenue: true, + ttl: 600, + ad: '', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + meta: { + advertiserDomains: [ + 'https://kobler.no' + ] + } + }, + { + requestId: '2ec0b40f-d3ca-4ba5-8ce3-48290565690f', + cpm: 6.71234, + currency: 'USD', + width: 580, + height: 400, + creativeId: 'fa2d5af7-2678-4204-9023-44c526160742', + dealId: '2783483223432342', + netRevenue: true, + ttl: 600, + ad: '', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + meta: { + advertiserDomains: [ + 'https://bid.kobler.no' + ] + } + } + ]; + expect(bids).to.deep.equal(expectedBids); + }); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function () { + spec.onBidWon({}); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should not trigger pixel if nurl is empty', function () { + spec.onBidWon({ + nurl: '' + }); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should trigger pixel with replaced nurl if nurl is not empty', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'NOK' + } + }); + spec.onBidWon({ + cpm: 8.341, + currency: 'NOK', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + adserverTargeting: { + hb_pb: 8 + } + }); + + expect(utils.triggerPixel.callCount).to.be.equal(1); + expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( + 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&sp_cur=NOK&asp=8&asp_cur=NOK' + ); + }); + }); + + describe('onTimeout', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if timeout data is not array', function () { + spec.onTimeout(null); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should not trigger pixel if timeout data is empty', function () { + spec.onTimeout([]); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it('Should trigger pixel with query parameters if timeout data not empty', function () { + spec.onTimeout([ + { + adUnitCode: 'adunit-code', + auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', + bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', + timeout: 100, + params: [ + { + placementId: 'xrwg62731', + } + ], + }, + { + adUnitCode: 'adunit-code-2', + auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', + bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', + timeout: 100, + params: [ + { + placementId: 'bc482234', + } + ], + } + ]); + + expect(utils.triggerPixel.callCount).to.be.equal(2); + expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( + 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + + 'placement_id=xrwg62731&page_url=' + encodeURIComponent(getRefererInfo().referer) + ); + expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( + 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + + 'placement_id=bc482234&page_url=' + encodeURIComponent(getRefererInfo().referer) + ); + }); + }); +}); diff --git a/test/spec/modules/komoonaBidAdapter_spec.js b/test/spec/modules/komoonaBidAdapter_spec.js deleted file mode 100644 index 3d62f91cae6..00000000000 --- a/test/spec/modules/komoonaBidAdapter_spec.js +++ /dev/null @@ -1,164 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/komoonaBidAdapter.js'; - -describe('Komoona.com Adapter Tests', function () { - const bidsRequest = [ - { - bidder: 'komoona', - params: { - placementId: '170577', - hbid: 'abc12345678', - }, - placementCode: 'div-gpt-ad-1460505748561-0', - transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', - sizes: [ - [300, 250] - ], - bidId: '2faedf1095f815', - bidderRequestId: '18065867f8ae39', - auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' - }, - { - bidder: 'komoona', - params: { - placementId: '281277', - hbid: 'abc12345678', - floorPrice: 0.5 - }, - placementCode: 'div-gpt-ad-1460505748561-0', - transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', - sizes: [ - [728, 90] - ], - bidId: '3c34e2367a3f59', - bidderRequestId: '18065867f8ae39', - auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' - }]; - - const bidsResponse = { - body: { - bids: [ - { - placementid: '170577', - uuid: '2faedf1095f815', - width: 300, - height: 250, - cpm: 0.51, - creative: '', - ttl: 360, - currency: 'USD', - netRevenue: true, - creativeId: 'd30b58c2ba' - } - ] - } - }; - - it('Verifies komoonaAdapter bidder code', function () { - expect(spec.code).to.equal('komoona'); - }); - - it('Verifies komoonaAdapter bid request validation', function () { - expect(spec.isBidRequestValid(bidsRequest[0])).to.equal(true); - expect(spec.isBidRequestValid(bidsRequest[1])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { hbid: 12345 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { placementid: 12345 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890, floorPrice: 0.8 } })).to.equal(true); - }); - - it('Verify komoonaAdapter build request', function () { - var startTime = new Date().getTime(); - - const request = spec.buildRequests(bidsRequest); - expect(request.url).to.equal('https://bidder.komoona.com/v1/GetSBids'); - expect(request.method).to.equal('POST'); - const requestData = JSON.parse(request.data); - - // bids object - let bids = requestData.bids; - expect(bids).to.have.lengthOf(2); - - // first bid request: no floor price - expect(bids[0].uuid).to.equal('2faedf1095f815'); - expect(bids[0].floorprice).to.be.undefined; - expect(bids[0].placementid).to.equal('170577'); - expect(bids[0].hbid).to.equal('abc12345678'); - expect(bids[0].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); - expect(bids[0].sizes).to.have.lengthOf(1); - expect(bids[0].sizes[0][0]).to.equal(300); - expect(bids[0].sizes[0][1]).to.equal(250); - - // second bid request: with floor price - expect(bids[1].uuid).to.equal('3c34e2367a3f59'); - expect(bids[1].floorprice).to.equal(0.5); - expect(bids[1].placementid).to.equal('281277'); - expect(bids[1].hbid).to.equal('abc12345678'); - expect(bids[1].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); - expect(bids[1]).to.have.property('sizes') - .that.is.an('array') - .of.length(1) - .that.deep.equals([[728, 90]]); - - // kbConf object - let kbConf = requestData.kbConf; - expect(kbConf.hdbdid).to.equal(bids[0].hbid); - expect(kbConf.hdbdid).to.equal(bids[1].hbid); - expect(kbConf.encode_bid).to.be.undefined; - // kbConf timezone and cb - expect(kbConf.cb).not.to.be.undefined; - expect(kbConf.ts_as).to.be.above(startTime - 1); - expect(kbConf.tz).to.equal(new Date().getTimezoneOffset()); - // kbConf bid ids - expect(kbConf.hb_placement_bidids) - .to.have.property(bids[0].placementid) - .that.equal(bids[0].uuid); - expect(kbConf.hb_placement_bidids) - .to.have.property(bids[1].placementid) - .that.equal(bids[1].uuid); - // kbConf floor price - expect(kbConf.hb_floors).not.to.have.property(bids[0].placementid) - expect(kbConf.hb_floors).to.have.property(bids[1].placementid).that.equal(bids[1].floorprice); - // kbConf placement ids - expect(kbConf.hb_placements).to.have.lengthOf(2); - expect(kbConf.hb_placements[0]).to.equal(bids[0].placementid); - expect(kbConf.hb_placements[1]).to.equal(bids[1].placementid); - }); - - it('Verify komoonaAdapter build response', function () { - const request = spec.buildRequests(bidsRequest); - const bids = spec.interpretResponse(bidsResponse, request); - - // 'server' return single bid - expect(bids).to.have.lengthOf(1); - - // verify bid object - const bid = bids[0]; - const responseBids = bidsResponse.body.bids; - - expect(bid.cpm).to.equal(responseBids[0].cpm); - expect(bid.ad).to.equal(responseBids[0].creative); - expect(bid.requestId).equal(responseBids[0].uuid); - expect(bid.uuid).equal(responseBids[0].uuid); - expect(bid.width).to.equal(responseBids[0].width); - expect(bid.height).to.equal(responseBids[0].height); - expect(bid.ttl).to.equal(responseBids[0].ttl); - expect(bid.currency).to.equal('USD'); - expect(bid.netRevenue).to.equal(true); - expect(bid.creativeId).to.equal(responseBids[0].creativeId); - }); - - it('Verifies komoonaAdapter sync options', function () { - // user sync disabled - expect(spec.getUserSyncs({})).to.be.undefined; - expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; - // user sync enabled - 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('https://s.komoona.com/sync/usync.html'); - }); -}); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js similarity index 57% rename from test/spec/modules/e_volutionBidAdapter_spec.js rename to test/spec/modules/krushmediaBidAdapter_spec.js index 447420616d1..fcdcc942290 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -1,28 +1,40 @@ import {expect} from 'chai'; -import {spec} from '../../../modules/e_volutionBidAdapter.js'; +import {spec} from '../../../modules/krushmediaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; -describe('EvolutionTechBidAdapter', function () { - let bid = { +describe('KrushmediabBidAdapter', function () { + const bid = { bidId: '23fhj33i987f', - bidder: 'e_volution', + bidder: 'krushmedia', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, params: { - placementId: 0, - traffic: 'banner' + key: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' } }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { + it('Should return true if there are bidId, params and key 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; + delete bid.params.key; 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; @@ -33,7 +45,7 @@ describe('EvolutionTechBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://service.e-volution.ai/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://ads4.krushmedia.com/?c=rtb&m=hb'); }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; @@ -45,12 +57,85 @@ describe('EvolutionTechBidAdapter', function () { 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'); - expect(placement.placementId).to.equal(0); + expect(placement).to.have.keys('key', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor'); + expect(placement.key).to.equal(783); expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + 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('key', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'bidFloor', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + 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('key', 'bidId', 'traffic', 'native', 'schain', 'bidFloor'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let data = serverRequest.data; @@ -78,7 +163,7 @@ describe('EvolutionTechBidAdapter', 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); @@ -88,6 +173,7 @@ describe('EvolutionTechBidAdapter', 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 = { @@ -108,7 +194,7 @@ describe('EvolutionTechBidAdapter', 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'); @@ -116,6 +202,7 @@ describe('EvolutionTechBidAdapter', 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 = { @@ -139,7 +226,7 @@ describe('EvolutionTechBidAdapter', 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); @@ -152,6 +239,7 @@ describe('EvolutionTechBidAdapter', 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 = { @@ -218,18 +306,29 @@ describe('EvolutionTechBidAdapter', function () { 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.e-volution.ai/?c=o&m=sync'); - } + 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('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&gdpr=1&gdpr_consent=ALL') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1NNN' + }); + 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('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&ccpa_consent=1NNN') }); }); }); diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js new file mode 100644 index 00000000000..5449de0c4de --- /dev/null +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -0,0 +1,399 @@ +import { expect, assert } from 'chai'; +import { spec } from 'modules/kubientBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +describe('KubientAdapter', function () { + let bidBanner = { + bidId: '2dd581a2b6281d', + bidder: 'kubient', + bidderRequestId: '145e1d6a7837c9', + params: { + zoneid: '5678' + }, + getFloor: function(params) { + return { + floor: 0.05, + currency: 'USD' + }; + }, + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.1', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let bidVideo = { + bidId: '1dd581a2b6281d', + bidder: 'kubient', + bidderRequestId: '245e1d6a7837c9', + params: { + zoneid: '5676' + }, + getFloor: function(params) { + return { + floor: 1.0, + currency: 'USD' + }; + }, + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e61', + schain: { + ver: '1.1', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + bidderCode: 'kubient', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + uspConsent: uspConsentData + }; + describe('buildRequestBanner', function () { + let serverRequests = spec.buildRequests([bidBanner], Object.assign({}, bidderRequest, {bids: [bidBanner]})); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequests).to.be.an('array'); + }); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'banner', 'schain'); + expect(adSlot.bidId).to.be.a('string').and.to.equal(bidBanner.bidId); + expect(adSlot.zoneId).to.be.a('string').and.to.equal(bidBanner.params.zoneid); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.banner).to.be.an('object'); + } + }); + } + }); + describe('buildRequestVideo', function () { + let serverRequests = spec.buildRequests([bidVideo], Object.assign({}, bidderRequest, {bids: [bidVideo]})); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequests).to.be.an('array'); + }); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'video', 'schain'); + expect(adSlot.bidId).to.be.a('string').and.to.equal(bidVideo.bidId); + expect(adSlot.zoneId).to.be.a('string').and.to.equal(bidVideo.params.zoneid); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.video).to.be.an('object'); + } + }); + } + }); + + describe('isBidRequestValid', function () { + it('Should return true when required params are found', function () { + expect(spec.isBidRequestValid(bidBanner)).to.be.true; + expect(spec.isBidRequestValid(bidVideo)).to.be.true; + }); + it('Should return false when required params are not found', function () { + expect(spec.isBidRequestValid(bidBanner)).to.be.true; + expect(spec.isBidRequestValid(bidVideo)).to.be.true; + }); + it('Should return false when params are not found', function () { + delete bidBanner.params; + expect(spec.isBidRequestValid(bidBanner)).to.be.false; + delete bidVideo.params; + expect(spec.isBidRequestValid(bidVideo)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret response', function () { + const serverResponse = { + body: + { + seatbid: [ + { + bid: [ + { + bidId: '000', + price: 1.5, + adm: '
test
', + creativeId: 'creativeId', + w: 300, + h: 250, + cur: 'USD', + netRevenue: false, + ttl: 360, + meta: {adomain: ['google.com', 'yahoo.com']} + } + ] + } + ] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'ad', 'creativeId', 'width', 'height', 'currency', 'netRevenue', 'ttl', 'meta'); + expect(dataItem.requestId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].bidId); + expect(dataItem.cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(dataItem.ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(dataItem.creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].creativeId); + expect(dataItem.width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(dataItem.height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(dataItem.currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].cur); + expect(dataItem.netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(serverResponse.body.seatbid[0].bid[0].netRevenue); + expect(dataItem.ttl).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].ttl); + expect(dataItem.meta).to.exist.and.to.be.a('object'); + expect(dataItem.meta.advertiserDomains).to.exist.and.to.be.a('array').and.to.equal(serverResponse.body.seatbid[0].bid[0].meta.adomain); + }); + + 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); + }); + }); + + describe('interpretResponse Video', function () { + it('Should interpret response', function () { + const serverResponse = { + body: + { + seatbid: [ + { + bid: [ + { + bidId: '000', + price: 1.5, + adm: '
test
', + creativeId: 'creativeId', + w: 300, + h: 250, + mediaType: VIDEO, + cur: 'USD', + netRevenue: false, + ttl: 360, + meta: {adomain: ['google.com', 'yahoo.com']} + } + ] + } + ] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'ad', 'creativeId', 'width', 'height', 'currency', 'netRevenue', 'ttl', 'meta', 'mediaType', 'vastXml'); + expect(dataItem.requestId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].bidId); + expect(dataItem.cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(dataItem.ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(dataItem.creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].creativeId); + expect(dataItem.width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(dataItem.height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(dataItem.currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].cur); + expect(dataItem.netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(serverResponse.body.seatbid[0].bid[0].netRevenue); + expect(dataItem.ttl).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].ttl); + expect(dataItem.meta).to.exist.and.to.be.a('object'); + expect(dataItem.meta.advertiserDomains).to.exist.and.to.be.a('array').and.to.equal(serverResponse.body.seatbid[0].bid[0].meta.adomain); + expect(dataItem.mediaType).to.exist.and.to.equal(VIDEO); + expect(dataItem.vastXml).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].adm); + }); + + 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); + }); + }); + + describe('getUserSyncs', function () { + it('should register the sync iframe without gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync iframe with gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync iframe with gdpr vendor', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 1, + vendorData: { + vendorConsents: { + 794: 1 + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + it('should register the sync image without gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync image with gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync image with gdpr vendor', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 2, + vendorData: { + vendor: { + consents: { + 794: 1 + } + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + }) +}); diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js deleted file mode 100644 index a236ac17d71..00000000000 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ /dev/null @@ -1,335 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/lemmaBidAdapter.js'; -import * as utils from 'src/utils.js'; -const constants = require('src/constants.json'); - -describe('lemmaBidAdapter', function() { - var bidRequests; - var videoBidRequests; - var bidResponses; - beforeEach(function() { - bidRequests = [{ - bidder: 'lemma', - mediaType: 'banner', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600] - ], - } - }, - params: { - pubId: 1001, - adunitId: 1, - currency: 'AUD', - geo: { - lat: '12.3', - lon: '23.7', - } - }, - sizes: [ - [300, 250], - [300, 600] - ] - }]; - videoBidRequests = [{ - code: 'video1', - mediaType: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - bidder: 'lemma', - params: { - pubId: 1001, - adunitId: 1, - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30 - } - } - }]; - bidResponses = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'bid': [{ - 'id': '74858439-49D7-4169-BA5D-44A046315B2F', - 'impid': '22bddb28db77d', - 'price': 1.3, - 'adm': '

lemma"Connecting Advertisers and Publishers directly"

', - 'adomain': ['amazon.com'], - 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', - 'cid': '22918', - 'crid': 'v55jutrh', - 'h': 250, - 'w': 300, - 'ext': {} - }] - }] - } - }; - }); - describe('implementation', function() { - describe('Bid validations', function() { - it('valid bid case', function() { - var validBid = { - bidder: 'lemma', - params: { - pubId: 1001, - adunitId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - it('invalid bid case', function() { - var isValid = spec.isBidRequestValid(); - expect(isValid).to.equal(false); - }); - it('invalid bid case: pubId not passed', function() { - var validBid = { - bidder: 'lemma', - params: { - adunitId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - it('invalid bid case: pubId is not number', function() { - var validBid = { - bidder: 'lemma', - params: { - pubId: '301', - adunitId: 1 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - it('invalid bid case: adunitId is not passed', function() { - var validBid = { - bidder: 'lemma', - params: { - pubId: 1001 - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - it('invalid bid case: video bid request mimes is not passed', function() { - var validBid = { - bidder: 'lemma', - params: { - pubId: 1001, - adunitId: 1, - video: { - skippable: true, - minduration: 5, - maxduration: 30 - } - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - validBid.params.video.mimes = []; - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - }); - describe('Request formation', function() { - it('buildRequests function should not modify original bidRequests object', function() { - var originalBidRequests = utils.deepClone(bidRequests); - var request = spec.buildRequests(bidRequests); - expect(bidRequests).to.deep.equal(originalBidRequests); - }); - it('Endpoint checking', function() { - var request = spec.buildRequests(bidRequests); - expect(request.url).to.equal('https://ads.lemmatechnologies.com/lemma/servad?pid=1001&aid=1'); - expect(request.method).to.equal('POST'); - }); - it('Request params check', function() { - var request = spec.buildRequests(bidRequests); - var data = JSON.parse(request.data); - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id - expect(data.imp[0].tagid).to.equal('1'); // tagid - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - }); - it('Request params check without mediaTypes object', function() { - var bidRequests = [{ - bidder: 'lemma', - params: { - pubId: 1001, - adunitId: 1, - currency: 'AUD' - }, - sizes: [ - [300, 250], - [300, 600] - ] - }]; - var request = spec.buildRequests(bidRequests); - var data = JSON.parse(request.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(600); // height - }); - it('Request params check: without tagId', function() { - delete bidRequests[0].params.adunitId; - var request = spec.buildRequests(bidRequests); - var data = JSON.parse(request.data); - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id - expect(data.imp[0].tagid).to.equal(undefined); // tagid - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - }); - it('Request params multi size format object check', function() { - var bidRequests = [{ - bidder: 'lemma', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600] - ], - } - }, - params: { - pubId: 1001, - adunitId: 1, - currency: 'AUD' - }, - sizes: [ - [300, 250], - [300, 600] - ] - }]; - /* case 1 - size passed in adslot */ - var request = spec.buildRequests(bidRequests); - var data = JSON.parse(request.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 */ - bidRequests[0].sizes = [ - [300, 600], - [300, 250] - ]; - bidRequests[0].mediaTypes = { - banner: { - sizes: [ - [300, 600], - [300, 250] - ] - } - }; - request = spec.buildRequests(bidRequests); - data = JSON.parse(request.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 */ - bidRequests[0].params.adunitId = 1; - bidRequests[0].sizes = [ - [300, 250], - [300, 600] - ]; - bidRequests[0].mediaTypes = { - banner: { - sizes: [ - [300, 250], - [300, 600] - ] - } - }; - request = spec.buildRequests(bidRequests); - data = JSON.parse(request.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('Request params currency check', function() { - var bidRequest = [{ - bidder: 'lemma', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600] - ], - } - }, - params: { - pubId: 1001, - adunitId: 1, - currency: 'AUD' - }, - sizes: [ - [300, 250], - [300, 600] - ] - }]; - /* case 1 - - currency specified in adunits - output: imp[0] use currency specified in bidRequests[0].params.currency - */ - var request = spec.buildRequests(bidRequest); - var data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[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.data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - }); - it('Request params check for video ad', function() { - var request = spec.buildRequests(videoBidRequests); - var data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0].tagid).to.equal('1'); - 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]); - 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']); - 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]); - }); - describe('Response checking', function() { - it('should check for valid response values', function() { - var request = spec.buildRequests(bidRequests); - var data = JSON.parse(request.data); - var response = spec.interpretResponse(bidResponses, request); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); - expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); - expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); - expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); - if (bidResponses.body.seatbid[0].bid[0].crid) { - expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); - } else { - expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); - } - expect(response[0].dealId).to.equal(bidResponses.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); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/lifestreetBidAdapter_spec.js b/test/spec/modules/lifestreetBidAdapter_spec.js deleted file mode 100644 index d66727da644..00000000000 --- a/test/spec/modules/lifestreetBidAdapter_spec.js +++ /dev/null @@ -1,232 +0,0 @@ -import { expect } from 'chai'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { spec } from 'modules/lifestreetBidAdapter.js'; - -describe('lifestreetBidAdapter', function() { - let bidRequests; - let videoBidRequests; - let bidResponses; - let videoBidResponses; - beforeEach(function() { - bidRequests = [ - { - bidder: 'lifestreet', - params: { - slot: 'slot166704', - adkey: '78c', - ad_size: '160x600' - }, - mediaTypes: { - banner: { - sizes: [ - [160, 600], - [300, 600] - ] - } - }, - sizes: [ - [160, 600], - [300, 600] - ] - } - ]; - - bidResponses = { - body: { - cpm: 0.1, - netRevenue: true, - content_type: 'display_flash', - width: 160, - currency: 'USD', - ttl: 86400, - content: '', - 'adid': '56380110', - 'cid': '44724710', - 'crid': '443801010', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'targeting': { - 'hb_bidder': 'luponmedia', - 'hb_pb': '0.40', - 'hb_size': '300x250' - }, - 'type': 'banner' - } - } - } - ], - 'seat': 'luponmedia' - } - ], - 'cur': 'USD', - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'ok', - 'bidder_status': [] - } - } - }; - - let expectedResponse = [ - { - 'requestId': '2a122246ef72ea', - 'cpm': '0.43', - 'width': 300, - 'height': 250, - 'creativeId': '443801010', - 'currency': 'USD', - 'dealId': '23425', - 'netRevenue': false, - 'ttl': 300, - 'referrer': '', - 'ad': ' ' - } - ]; - - let bidderRequest = { - 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' - }; - - 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 noBidResponse = []; - - let noBidBidderRequest = { - 'data': '{"site":{"page":""}}' - } - let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); - expect(noBidResult.length).to.equal(0); - }); - }); - - describe('getUserSyncs', function () { - const bidResponse1 = { - 'body': { - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'ok', - 'bidder_status': [ - { - 'bidder': 'luponmedia', - 'no_cookie': true, - 'usersync': { - 'url': 'https://adxpremium.services/api/usersync', - 'type': 'redirect' - } - }, - { - 'bidder': 'luponmedia', - 'no_cookie': true, - 'usersync': { - 'url': 'https://adxpremium.services/api/iframeusersync', - 'type': 'iframe' - } - } - ] - } - } - } - }; - - const bidResponse2 = { - 'body': { - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'no_cookie', - 'bidder_status': [] - } - } - } - }; - - it('should use a sync url from first response (pixel and iframe)', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'https://adxpremium.services/api/usersync' - }, - { - type: 'iframe', - url: 'https://adxpremium.services/api/iframeusersync' - } - ]); - }); - - it('handle empty response (e.g. timeout)', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); - expect(syncs).to.deep.equal([]); - }); - - it('returns empty syncs when not pixel enabled and not iframe enabled', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); - expect(syncs).to.deep.equal([]); - }); - - it('returns pixel syncs when pixel enabled and not iframe enabled', function() { - resetUserSync(); - - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'https://adxpremium.services/api/usersync' - } - ]); - }); - - it('returns iframe syncs when not pixel enabled and iframe enabled', function() { - resetUserSync(); - - const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'https://adxpremium.services/api/iframeusersync' - } - ]); - }); - }); - - describe('hasValidSupplyChainParams', function () { - it('returns true if schain is valid', function () { - const schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'novi.ba', - 'sid': '199424', - 'hp': 1 - } - ] - }; - - const checkSchain = hasValidSupplyChainParams(schain); - expect(checkSchain).to.equal(true); - }); - - it('returns false if schain is invalid', function () { - const schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'invalid': 'novi.ba' - } - ] - }; - - const checkSchain = hasValidSupplyChainParams(schain); - expect(checkSchain).to.equal(false); - }); - }); -}); diff --git a/test/spec/modules/madvertiseBidAdapter_spec.js b/test/spec/modules/madvertiseBidAdapter_spec.js index 041b49ef69e..466d30acdd3 100644 --- a/test/spec/modules/madvertiseBidAdapter_spec.js +++ b/test/spec/modules/madvertiseBidAdapter_spec.js @@ -1,57 +1,57 @@ import {expect} from 'chai'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import {spec} from 'modules/madvertiseBidAdapter.js'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; +import {spec} from 'modules/madvertiseBidAdapter'; -describe('madvertise adapater', function () { - describe('Test validate req', function () { - it('should accept minimum valid bid', function () { +describe('madvertise adapater', () => { + describe('Test validate req', () => { + it('should accept minimum valid bid', () => { let bid = { bidder: 'madvertise', sizes: [[728, 90]], params: { - s: 'test' + zoneId: 'test' } }; const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); + expect(isValid).to.equal(false); }); - it('should reject no sizes', function () { + it('should reject no sizes', () => { let bid = { bidder: 'madvertise', params: { - s: 'test' + zoneId: 'test' } }; const isValid = spec.isBidRequestValid(bid); expect(isValid).to.equal(false); }); - it('should reject empty sizes', function () { + it('should reject empty sizes', () => { let bid = { bidder: 'madvertise', sizes: [], params: { - s: 'test' + zoneId: 'test' } }; const isValid = spec.isBidRequestValid(bid); expect(isValid).to.equal(false); }); - it('should reject wrong format sizes', function () { + it('should reject wrong format sizes', () => { let bid = { bidder: 'madvertise', sizes: [['728x90']], params: { - s: 'test' + zoneId: 'test' } }; const isValid = spec.isBidRequestValid(bid); expect(isValid).to.equal(false); }); - it('should reject no params', function () { + it('should reject no params', () => { let bid = { bidder: 'madvertise', sizes: [[728, 90]] @@ -60,7 +60,7 @@ describe('madvertise adapater', function () { expect(isValid).to.equal(false); }); - it('should reject missing s', function () { + it('should reject missing s', () => { let bid = { bidder: 'madvertise', params: {} @@ -71,7 +71,7 @@ describe('madvertise adapater', function () { }); }); - describe('Test build request', function () { + describe('Test build request', () => { beforeEach(function () { let mockConfig = { consentManagement: { @@ -97,13 +97,13 @@ describe('madvertise adapater', function () { auctionId: '18fd8b8b0bd757', bidderRequestId: '418b37f85e772c', params: { - s: 'test', + zoneId: 'test', } }]; - it('minimum request with gdpr consent', function () { + it('minimum request with gdpr consent', () => { let bidderRequest = { gdprConsent: { - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + consentString: 'CO_5mtSPHOmEIAsAkBFRBOCsAP_AAH_AAAqIHQgB7SrERyNAYWB5gusAKYlfQAQCA2AABAYdASgJQQBAMJYEkGAIuAnAACAKAAAEIHQAAAAlCCmABAEAAIABBSGMAQgABZAAIiAEEAATAABACAABGYCSCAIQjIAAAAEAgEKEAAoAQGBAAAEgBABAAAogACADAgXmACIKkQBAkBAYAkAYQAogAhAAAAAIAAAAAAAKAABAAAghAAQQAAAAAAAAAgAAAAABAAAAAAAAQAAAAAAAAABAAgAAAAAAAAAIAAAAAAAAAAAAAAAABAAAAAAAAAAAQCAKCgBgEQALgAqkJADAIgAXABVIaACAAERABAACKgAgABA', vendorData: {}, gdprApplies: true } @@ -114,15 +114,15 @@ describe('madvertise adapater', function () { expect(req[0]).to.have.property('method'); expect(req[0].method).to.equal('GET'); expect(req[0]).to.have.property('url'); - expect(req[0].url).to.contain('https://mobile.mng-ads.com/?rt=bid_request&v=1.0'); - expect(req[0].url).to.contain(`&s=test`); + expect(req[0].url).to.contain('//mobile.mng-ads.com/?rt=bid_request&v=1.0'); + expect(req[0].url).to.contain(`&zoneId=test`); expect(req[0].url).to.contain(`&sizes[0]=728x90`); expect(req[0].url).to.contain(`&gdpr=1`); expect(req[0].url).to.contain(`&consent[0][format]=IAB`); - expect(req[0].url).to.contain(`&consent[0][value]=BOJ/P2HOJ/P2HABABMAAAAAZ+A==`) + expect(req[0].url).to.contain(`&consent[0][value]=CO_5mtSPHOmEIAsAkBFRBOCsAP_AAH_AAAqIHQgB7SrERyNAYWB5gusAKYlfQAQCA2AABAYdASgJQQBAMJYEkGAIuAnAACAKAAAEIHQAAAAlCCmABAEAAIABBSGMAQgABZAAIiAEEAATAABACAABGYCSCAIQjIAAAAEAgEKEAAoAQGBAAAEgBABAAAogACADAgXmACIKkQBAkBAYAkAYQAogAhAAAAAIAAAAAAAKAABAAAghAAQQAAAAAAAAAgAAAAABAAAAAAAAQAAAAAAAAABAAgAAAAAAAAAIAAAAAAAAAAAAAAAABAAAAAAAAAAAQCAKCgBgEQALgAqkJADAIgAXABVIaACAAERABAACKgAgABA`) }); - it('minimum request without gdpr consent', function () { + it('minimum request without gdpr consent', () => { let bidderRequest = {}; const req = spec.buildRequests(bid, bidderRequest); @@ -130,8 +130,8 @@ describe('madvertise adapater', function () { expect(req[0]).to.have.property('method'); expect(req[0].method).to.equal('GET'); expect(req[0]).to.have.property('url'); - expect(req[0].url).to.contain('https://mobile.mng-ads.com/?rt=bid_request&v=1.0'); - expect(req[0].url).to.contain(`&s=test`); + expect(req[0].url).to.contain('//mobile.mng-ads.com/?rt=bid_request&v=1.0'); + expect(req[0].url).to.contain(`&zoneId=test`); expect(req[0].url).to.contain(`&sizes[0]=728x90`); expect(req[0].url).not.to.contain(`&gdpr=1`); expect(req[0].url).not.to.contain(`&consent[0][format]=`); @@ -139,8 +139,8 @@ describe('madvertise adapater', function () { }); }); - describe('Test interpret response', function () { - it('General banner response', function () { + describe('Test interpret response', () => { + it('General banner response', () => { let bid = { bidder: 'madvertise', sizes: [[728, 90]], @@ -150,7 +150,7 @@ describe('madvertise adapater', function () { auctionId: '18fd8b8b0bd757', bidderRequestId: '418b37f85e772c', params: { - s: 'test', + zoneId: 'test', connection_type: 'WIFI', age: 25, } @@ -165,7 +165,8 @@ describe('madvertise adapater', function () { dealId: 'DEAL_ID', ttl: 180, currency: 'EUR', - netRevenue: true + netRevenue: true, + adomain: ['madvertise.com'] }}, {bidId: bid.bidId}); expect(resp).to.exist.and.to.be.a('array'); @@ -179,8 +180,9 @@ describe('madvertise adapater', function () { expect(resp[0]).to.have.property('netRevenue', true); expect(resp[0]).to.have.property('currency', 'EUR'); expect(resp[0]).to.have.property('dealId', 'DEAL_ID'); + // expect(resp[0].adomain).to.deep.equal(['madvertise.com']); }); - it('No response', function () { + it('No response', () => { let bid = { bidder: 'madvertise', sizes: [[728, 90]], @@ -190,7 +192,7 @@ describe('madvertise adapater', function () { auctionId: '18fd8b8b0bd757', bidderRequestId: '418b37f85e772c', params: { - s: 'test', + zoneId: 'test', connection_type: 'WIFI', age: 25, } diff --git a/test/spec/modules/malltvAnalyticsAdapter_spec.js b/test/spec/modules/malltvAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..599ac6e4256 --- /dev/null +++ b/test/spec/modules/malltvAnalyticsAdapter_spec.js @@ -0,0 +1,540 @@ +import { + malltvAnalyticsAdapter, parseBidderCode, parseAdUnitCode, + ANALYTICS_VERSION, BIDDER_STATUS, DEFAULT_SERVER +} from 'modules/malltvAnalyticsAdapter.js' +import { expect } from 'chai' +import { getCpmInEur } from '../../../modules/malltvAnalyticsAdapter' +import events from 'src/events' +import constants from 'src/constants.json' + +const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e' +const propertyId = '123456' +const server = 'https://analytics.server.url/v1' + +describe('Malltv Prebid AnalyticsAdapter Testing', function () { + describe('event tracking and message cache manager', function () { + beforeEach(function () { + const configOptions = { propertyId } + + sinon.stub(events, 'getEvents').returns([]) + malltvAnalyticsAdapter.enableAnalytics({ + provider: 'malltvAnalytics', + options: configOptions + }) + }) + + afterEach(function () { + malltvAnalyticsAdapter.disableAnalytics() + events.getEvents.restore() + }) + + describe('#getCpmInEur()', function() { + it('should get bid cpm as currency is EUR', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'malltv', + bidderCode: 'MALLTV', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1' + }, + ] + const result = getCpmInEur(receivedBids[0]) + expect(result).to.equal(0.1) + }) + }) + + describe('#parseBidderCode()', function() { + it('should get lower case bidder code from bidderCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'malltv', + bidderCode: 'MALLTV', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1' + }, + ] + const result = parseBidderCode(receivedBids[0]) + expect(result).to.equal('malltv') + }) + + it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'MALLTV', + bidderCode: '', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1' + }, + ] + const result = parseBidderCode(receivedBids[0]) + expect(result).to.equal('malltv') + }) + }) + + describe('#parseAdUnitCode()', function() { + it('should get lower case adUnit code from adUnitCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'ADUNIT', + bidder: 'malltv', + bidderCode: 'MALLTV', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1' + }, + ] + const result = parseAdUnitCode(receivedBids[0]) + expect(result).to.equal('adunit') + }) + }) + + describe('#getCachedAuction()', function() { + const existing = {timeoutBids: [{}]} + malltvAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing + + it('should get the existing cached object if it exists', function() { + const result = malltvAnalyticsAdapter.getCachedAuction('test_auction_id') + + expect(result).to.equal(existing) + }) + + it('should create a new object and store it in the cache on cache miss', function() { + const result = malltvAnalyticsAdapter.getCachedAuction('no_such_id') + + expect(result).to.deep.include({ + timeoutBids: [], + }) + }) + }) + + describe('when formatting JSON payload sent to backend', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'malltv', + bidderCode: 'malltv', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1', + vastUrl: null + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'gjirafa', + bidderCode: 'gjirafa', + requestId: 'b2c3d4e5', + timeToRespond: 100, + cpm: 0.08, + currency: 'EUR', + originalCpm: 0.08, + originalCurrency: 'EUR', + ad: 'fake ad2', + vastUrl: null + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'malltv', + bidderCode: 'malltv', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'EUR', + originalCpm: 0.09, + originalCurrency: 'EUR', + ad: 'fake ad3', + vastUrl: null + }, + ] + const highestCpmBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'malltv', + bidderCode: 'malltv', + // No requestId + timeToRespond: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + ad: 'fake ad1', + vastUrl: null + } + ] + const noBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'malltv', + bidderCode: 'malltv', + bidId: 'a1b2c3d4', + } + ] + const timeoutBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'gjirafa', + bidderCode: 'gjirafa', + bidId: '00123d4c', + } + ] + const withoutOriginalCpmBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'malltv', + bidderCode: 'malltv', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.29, + currency: 'EUR', + originalCpm: '', + originalCurrency: 'EUR', + ad: 'fake ad3' + }, + ] + const withoutOriginalCurrencyBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'malltv', + bidderCode: 'malltv', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'EUR', + originalCpm: 0.09, + originalCurrency: '', + ad: 'fake ad3' + }, + ] + + function assertHavingRequiredMessageFields(message) { + expect(message).to.include({ + analyticsVersion: ANALYTICS_VERSION, + auctionId: auctionId, + propertyId: propertyId, + prebidVersion: '$prebid.version$', + }) + } + + describe('#createCommonMessage', function() { + it('should correctly serialize some common fields', function() { + const message = malltvAnalyticsAdapter.createCommonMessage(auctionId) + + assertHavingRequiredMessageFields(message) + }) + }) + + describe('#serializeBidResponse', function() { + it('should handle BID properly and serialize bid price related fields', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID) + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + cpmEur: 0.1, + }) + }) + + it('should handle NO_BID properly and set status to noBid', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID) + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + }) + }) + + it('should handle BID_WON properly and serialize bid price related fields', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID_WON) + + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + cpmEur: 0.1, + }) + }) + + it('should handle TIMEOUT properly and set status to timeout and isTimeout to true', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT) + + expect(result).to.include({ + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + }) + }) + + it('should handle BID_WON properly and fill originalCpm field with cpm in missing originalCpm case', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(withoutOriginalCpmBids[0], BIDDER_STATUS.BID_WON) + + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 120, + cpm: 0.29, + currency: 'EUR', + originalCpm: 0.29, + originalCurrency: 'EUR', + cpmEur: 0.29, + }) + }) + + it('should handle BID_WON properly and fill originalCurrency field with currency in missing originalCurrency case', function() { + const result = malltvAnalyticsAdapter.serializeBidResponse(withoutOriginalCurrencyBids[0], BIDDER_STATUS.BID_WON) + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 120, + cpm: 0.09, + currency: 'EUR', + originalCpm: 0.09, + originalCurrency: 'EUR', + cpmEur: 0.09, + }) + }) + }) + + describe('#addBidResponseToMessage()', function() { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + const message = { + adUnits: {} + } + malltvAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID) + + expect(message.adUnits).to.deep.include({ + 'adunit_2': { + 'malltv': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + } + } + }) + }) + }) + + describe('#createBidMessage()', function() { + it('should format auction message sent to the backend', function() { + const args = { + auctionId: auctionId, + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + adUnitCodes: ['adunit_1', 'adunit_2'], + bidsReceived: receivedBids, + noBids: noBids + } + + const result = malltvAnalyticsAdapter.createBidMessage(args, highestCpmBids, timeoutBids) + + assertHavingRequiredMessageFields(result) + expect(result).to.deep.include({ + auctionElapsed: 100, + timeout: 3000, + adUnits: { + 'adunit_1': { + 'malltv': { + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'EUR', + originalCpm: 0.1, + originalCurrency: 'EUR', + cpmEur: 0.1, + vastUrl: null + }, + 'gjirafa': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 100, + cpm: 0.08, + currency: 'EUR', + originalCpm: 0.08, + originalCurrency: 'EUR', + cpmEur: 0.08, + vastUrl: null + } + }, + 'adunit_2': { + // this bid result exists in both bid and noBid arrays and should be treated as bid + 'malltv': { + prebidWon: false, + isTimeout: false, + time: 120, + cpm: 0.09, + currency: 'EUR', + originalCpm: 0.09, + originalCurrency: 'EUR', + cpmEur: 0.09, + status: BIDDER_STATUS.BID, + vastUrl: null + }, + 'gjirafa': { + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + } + } + } + }) + }) + }) + + describe('#handleBidTimeout()', function() { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + malltvAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] } + const args = [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + + malltvAnalyticsAdapter.handleBidTimeout(args) + const result = malltvAnalyticsAdapter.getCachedAuction('test_timeout_auction_id') + expect(result).to.deep.include({ + timeoutBids: [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + }) + }) + }) + }) + }) + + describe('Malltv Analytics Adapter track handler ', function () { + const configOptions = { propertyId } + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]) + malltvAnalyticsAdapter.enableAnalytics({ + provider: 'malltvAnalytics', + options: configOptions + }) + }) + + afterEach(function () { + malltvAnalyticsAdapter.disableAnalytics() + events.getEvents.restore() + }) + + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + sinon.spy(malltvAnalyticsAdapter, 'handleBidTimeout') + events.emit(constants.EVENTS.BID_TIMEOUT, {}) + sinon.assert.callCount(malltvAnalyticsAdapter.handleBidTimeout, 1) + malltvAnalyticsAdapter.handleBidTimeout.restore() + }) + + it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + sinon.spy(malltvAnalyticsAdapter, 'handleAuctionEnd') + events.emit(constants.EVENTS.AUCTION_END, {}) + sinon.assert.callCount(malltvAnalyticsAdapter.handleAuctionEnd, 1) + malltvAnalyticsAdapter.handleAuctionEnd.restore() + }) + }) + + describe('enableAnalytics and config parser', function () { + const configOptions = { propertyId, server } + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]) + malltvAnalyticsAdapter.enableAnalytics({ + provider: 'malltvAnalytics', + options: configOptions + }) + }) + + afterEach(function () { + malltvAnalyticsAdapter.disableAnalytics() + events.getEvents.restore() + }) + + it('should parse config correctly with optional values', function () { + const { options, propertyId, server } = malltvAnalyticsAdapter.getAnalyticsOptions() + + expect(options).to.deep.equal(configOptions) + expect(propertyId).to.equal(configOptions.propertyId) + expect(server).to.equal(configOptions.server) + }) + + it('should not enable Analytics when propertyId is missing', function() { + const configOptions = { + options: { } + } + + const isConfigValid = malltvAnalyticsAdapter.initConfig(configOptions) + expect(isConfigValid).to.equal(false) + }) + + it('should use DEFAULT_SERVER when server is missing', function () { + const configOptions = { + options: { + propertyId + } + } + malltvAnalyticsAdapter.initConfig(configOptions) + expect(malltvAnalyticsAdapter.getAnalyticsOptions().server).to.equal(DEFAULT_SERVER) + }) + }) +}) diff --git a/test/spec/modules/malltvBidAdapter_spec.js b/test/spec/modules/malltvBidAdapter_spec.js new file mode 100644 index 00000000000..c31e91992f7 --- /dev/null +++ b/test/spec/modules/malltvBidAdapter_spec.js @@ -0,0 +1,185 @@ +import { expect } from 'chai'; +import { spec } from 'modules/malltvBidAdapter'; + +describe('malltvAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId or placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: {} + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'malltv', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[300, 250]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://central.mall.tv/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('300x250'); + }); + }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://central.mall.tv/bid', + 'data': { + 'sizes': '300x250', + 'adUnitId': 'rectangle', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 300, + 'Height': 250, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '
Test ad
', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360, + 'ADomain': ['somedomain.com'] + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad', + 'vastUrl', + 'mediaType', + 'meta' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function (key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + + it('all values correct', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('123abc'); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].netRevenue).to.equal(false); + expect(result[0].ttl).to.equal(360); + expect(result[0].referrer).to.equal('http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true'); + expect(result[0].ad).to.equal('
Test ad
'); + expect(result[0].meta.advertiserDomains).to.deep.equal(['somedomain.com']); + }) + }); +}); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index d9ab3c69a24..579f41e620d 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -1,19 +1,18 @@ import {expect} from 'chai'; -import {spec} from 'modules/mantisBidAdapter.js'; +import {spec, storage} from 'modules/mantisBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {sfPostMessage, iframePostMessage} from 'modules/mantisBidAdapter'; describe('MantisAdapter', function () { const adapter = newBidder(spec); - let sandbox; + const sandbox = sinon.sandbox.create(); let clock; - beforeEach(function() { - sandbox = sinon.sandbox.create(); + beforeEach(function () { clock = sandbox.useFakeTimers(); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); @@ -171,13 +170,12 @@ describe('MantisAdapter', function () { }); it('use storage uuid', function () { - window.localStorage.setItem('mantis:uuid', 'bar'); + sandbox.stub(storage, 'hasLocalStorage').callsFake(() => true); + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('mantis:uuid').returns('bar'); const request = spec.buildRequests(bidRequests); expect(request.url).to.include('uuid=bar'); - - window.localStorage.removeItem('mantis:uuid'); }); it('detect amp', function () { @@ -249,6 +247,9 @@ describe('MantisAdapter', function () { ad: '', creativeId: 'view', netRevenue: true, + meta: { + advertiserDomains: [] + }, currency: 'USD' } ]; @@ -268,6 +269,7 @@ describe('MantisAdapter', function () { bid: 'bid', cpm: 1, view: 'view', + domains: ['foobar.com'], width: 300, height: 250, html: '' @@ -286,6 +288,9 @@ describe('MantisAdapter', function () { ad: '', creativeId: 'view', netRevenue: true, + meta: { + advertiserDomains: ['foobar.com'] + }, currency: 'USD' } ]; @@ -305,6 +310,7 @@ describe('MantisAdapter', function () { cpm: 1, view: 'view', width: 300, + domains: ['foobar.com'], height: 250, html: '' } @@ -322,15 +328,22 @@ describe('MantisAdapter', function () { ad: '', creativeId: 'view', netRevenue: true, + meta: { + advertiserDomains: ['foobar.com'] + }, currency: 'USD' } ]; let bidderRequest; + sandbox.stub(storage, 'hasLocalStorage').returns(true); + const spy = sandbox.spy(storage, 'setDataInLocalStorage'); + let result = spec.interpretResponse(response, {bidderRequest}); + + expect(spy.calledWith('mantis:uuid', 'uuid')); expect(result[0]).to.deep.equal(expectedResponse[0]); expect(window.mantis_uuid).to.equal(response.body.uuid); - expect(window.localStorage.getItem('mantis:uuid')).to.equal(response.body.uuid); }); it('no ads returned', function () { diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index b4c2fe68f34..cf074b0f3d6 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,11 +1,41 @@ -import {spec} from '../../../modules/marsmediaBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import * as sinon from 'sinon'; +import { spec } from 'modules/marsmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -var r1adapter = spec; +var marsAdapter = spec; describe('marsmedia adapter tests', function () { + let element, win; + 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 + }; this.defaultBidderRequest = { 'refererInfo': { 'referer': 'Reference Page', @@ -15,28 +45,40 @@ describe('marsmedia adapter tests', function () { ] } }; + + this.defaultBidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('Unit-Code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); }); describe('Verify 1.0 POST Banner Bid Request', function () { it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - '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); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -52,11 +94,11 @@ describe('marsmedia adapter tests', function () { expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); }); - it('interpretResponse works', function() { + /* it('interpretResponse works', function() { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-0', + 'impid': 'Unit-Code', 'w': 300, 'h': 250, 'adm': '
My Compelling Ad
', @@ -67,7 +109,7 @@ describe('marsmedia adapter tests', function () { ] }; - var bannerBids = r1adapter.interpretResponse(bidList); + var bannerBids = marsAdapter.interpretResponse(bidList); expect(bannerBids.length).to.equal(1); const bid = bannerBids[0]; @@ -78,7 +120,7 @@ describe('marsmedia adapter tests', function () { 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() { @@ -95,7 +137,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'sizes': [ [300, 250] ], @@ -107,7 +149,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -132,7 +174,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': 'https://example.com/', 'adomain': [ @@ -147,7 +189,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -166,7 +208,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': '', 'adomain': [ @@ -181,7 +223,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -199,26 +241,6 @@ describe('marsmedia adapter tests', function () { describe('misc buildRequests', function() { it('should send GDPR Consent data to Marsmedia tag', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - '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 = { @@ -226,13 +248,50 @@ describe('marsmedia adapter tests', function () { 'consentString': consentString }; - var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, gdprBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.user.ext.consent).to.equal(consentString); expect(openrtbRequest.regs.ext.gdpr).to.equal(true); }); + it('should have CCPA Consent if defined', function () { + const ccpaBidderRequest = this.defaultBidderRequest; + ccpaBidderRequest.uspConsent = '1YYN'; + const bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, ccpaBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should submit coppa if set in config', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should process floors module if available', function() { + const floorBidderRequest = this.defaultBidRequestList; + const floorInfo = { + currency: 'USD', + floor: 1.20 + }; + floorBidderRequest[0].getFloor = () => floorInfo; + const request = marsAdapter.buildRequests(floorBidderRequest, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(1.20); + }); + + it('should have 0 bidfloor value', function() { + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(0); + }); + it('prefer 2.0 sizes', function () { var bidRequestList = [ { @@ -245,7 +304,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 600]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'sizes': [[300, 250]], 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', @@ -255,7 +314,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); @@ -274,7 +333,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -283,7 +342,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -297,7 +356,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -306,7 +365,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -320,7 +379,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -329,35 +388,15 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.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': 'marsmedia', - '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); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); dntStub.restore(); @@ -378,7 +417,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['600', '300'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -387,7 +426,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.equal(600); @@ -407,7 +446,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['badWidth', 'badHeight'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -416,7 +455,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -435,7 +474,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -444,7 +483,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -453,52 +492,74 @@ describe('marsmedia adapter tests', function () { it('should return empty site data when refererInfo is missing', function() { delete this.defaultBidderRequest.refererInfo; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - '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); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, 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(''); }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); + }); + }); + + 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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].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 = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); }); it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { this.defaultBidderRequest.refererInfo.stack = []; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - '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); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); @@ -508,24 +569,7 @@ describe('marsmedia adapter tests', function () { it('should secure correctly', function() { this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - '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); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].secure).to.equal(1); @@ -551,9 +595,12 @@ describe('marsmedia adapter tests', function () { 'params': { 'zoneId': 9999 }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -563,7 +610,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); @@ -571,7 +618,7 @@ describe('marsmedia adapter tests', function () { describe('misc interpretResponse', function () { it('No bid response', function() { - var noBidResponse = r1adapter.interpretResponse({ + var noBidResponse = marsAdapter.interpretResponse({ 'body': '' }); expect(noBidResponse.length).to.equal(0); @@ -589,22 +636,22 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'bannerDiv' + 'adUnitCode': 'Unit-Code' }; it('should return true when required params found', function () { - expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId missing', function () { delete bid.params.zoneId; - expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(false); }); }); describe('getUserSyncs', function () { it('returns an empty string', function () { - expect(r1adapter.getUserSyncs()).to.deep.equal([]); + expect(marsAdapter.getUserSyncs()).to.deep.equal([]); }); }); diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js new file mode 100644 index 00000000000..a4a87ce113f --- /dev/null +++ b/test/spec/modules/mass_spec.js @@ -0,0 +1,151 @@ +import { expect } from 'chai'; +import { + init, + addBidResponseHook, + addListenerOnce, + isMassBid, + useDefaultMatch, + useDefaultRender, + updateRenderers, + listenerAdded, + isEnabled +} from 'modules/mass'; +import { logInfo } from 'src/utils.js'; + +// mock a MASS bid: +const mockedMassBids = [ + { + bidder: 'ix', + bidId: 'mass-bid-1', + requestId: 'mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, + { + bidder: 'ix', + bidId: 'mass-bid-2', + requestId: 'mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: { + mass: true + } + }, +]; + +// mock non-MASS bids: +const mockedNonMassBids = [ + { + bidder: 'ix', + bidId: 'non-mass-bid-1', + requstId: 'non-mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: '', + meta: { + mass: true + } + }, + { + bidder: 'ix', + bidId: 'non-mass-bid-2', + requestId: 'non-mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, +]; + +// mock bidder request: +const mockedBidderRequest = { + bidderCode: 'ix', + bidderRequestId: 'bidder-request-id-1' +}; + +const noop = function() {}; + +describe('MASS Module', function() { + let bidderRequest = Object.assign({}, mockedBidderRequest); + + it('should be enabled by default', function() { + expect(isEnabled).to.equal(true); + }); + + it('can be disabled', function() { + init({enabled: false}); + expect(isEnabled).to.equal(false); + }); + + it('should only affect MASS bids', function() { + init({renderUrl: 'https://...'}); + mockedNonMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should only update the ad markup field', function() { + init({renderUrl: 'https://...'}); + mockedMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid.ad).to.not.equal(originalBid.ad); + + delete bid.ad; + delete originalBid.ad; + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should add a message listener', function() { + addListenerOnce(); + expect(listenerAdded).to.equal(true); + }); + + it('should support custom renderers', function() { + init({ + renderUrl: 'https://...', + custom: [ + { + dealIdPattern: /abc/, + render: function() {} + } + ] + }); + + const renderers = updateRenderers(); + + expect(renderers.length).to.equal(2); + }); + + it('should match bids by deal ID with the default matcher', function() { + const match = useDefaultMatch(/abc/); + + expect(match({dealId: 'abc'})).to.equal(true); + expect(match({dealId: 'xyz'})).to.equal(false); + }); + + it('should have a default renderer', function() { + const render = useDefaultRender('https://example.com/render.js', 'abc'); + render({}); + + expect(window.abc.loaded).to.equal(true); + expect(window.abc.queue.length).to.equal(1); + }); +}); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js new file mode 100644 index 00000000000..0f0da6032eb --- /dev/null +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -0,0 +1,394 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/mathildeadsBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'mathildeads' + +describe('MathildeAdsBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + 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: {} + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + 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 () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + 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://endpoint2.mathilde-ads.com/pbjs'); + }); + + 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', + '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.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 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); + + 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 () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + 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'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + 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 = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + 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', '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(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + 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'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + 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', 'meta'); + 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'); + 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 = { + 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() { + 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://cs2.mathilde-ads.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://cs2.mathilde-ads.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/meazyBidAdapter_spec.js b/test/spec/modules/meazyBidAdapter_spec.js deleted file mode 100644 index 2c24791f515..00000000000 --- a/test/spec/modules/meazyBidAdapter_spec.js +++ /dev/null @@ -1,177 +0,0 @@ -import * as utils from 'src/utils.js'; -import { expect } from 'chai'; -import { spec } from 'modules/meazyBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -const MEAZY_PID = '6910b7344ae566a1' -const VALID_ENDPOINT = `https://rtb-filter.meazy.co/pbjs?host=${utils.getOrigin()}&api_key=${MEAZY_PID}`; - -const bidderRequest = { - refererInfo: { - referer: 'page', - stack: ['page', 'page1'] - } -}; - -const bidRequest = { - bidder: 'meazy', - adUnitCode: 'test-div', - sizes: [[300, 250], [300, 600]], - params: { - pid: MEAZY_PID - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', -}; - -const bidContent = { - 'id': '30b31c1838de1e', - 'bidid': '9780a52ff05c0e92780f5baf9cf3f4e8', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': 'ccf05fb8effb3d02', - 'impid': 'B19C34BBD69DAF9F', - 'burl': 'https://track.meazy.co/imp?bidid=9780a52ff05c0e92780f5baf9cf3f4e8&user=fdc401a2-92f1-42bd-ac22-d570520ad0ec&burl=1&ssp=5&project=2&cost=${AUCTION_PRICE}', - '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 09d997e9349..0b5c4d00f53 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -1,6 +1,7 @@ import {assert} from 'chai'; import {spec} from 'modules/mediaforceBidAdapter.js'; import * as utils from '../../../src/utils.js'; +import {BANNER, NATIVE} from '../../../src/mediaTypes.js'; describe('mediaforce bid adapter', function () { let sandbox; @@ -19,14 +20,14 @@ describe('mediaforce bid adapter', function () { } const language = getLanguage(); - const baseUrl = 'https://rtb.mfadsrvr.com' + const baseUrl = 'https://rtb.mfadsrvr.com'; describe('isBidRequestValid()', function () { const defaultBid = { bidder: 'mediaforce', params: { property: '10433394', - bidfloor: 0.3, + bidfloor: 0, }, }; @@ -56,17 +57,6 @@ describe('mediaforce bid adapter', function () { bid.params = {publisher_id: 2, placement_id: '123'}; assert.equal(spec.isBidRequestValid(bid), true); }); - - it('should return false when mediaTypes == native passed (native is not supported yet)', function () { - let bid = utils.deepClone(defaultBid); - bid.mediaTypes = { - native: { - sizes: [[300, 250]] - } - }; - bid.params = {publisher_id: 2, placement_id: '123'}; - assert.equal(spec.isBidRequestValid(bid), true); - }); }); describe('buildRequests()', function () { @@ -76,14 +66,71 @@ describe('mediaforce bid adapter', function () { publisher_id: 'pub123', placement_id: '202', }, + nativeParams: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } + }, mediaTypes: { banner: { sizes: [[300, 250]] + }, + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } } }, transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', }; + const multiBid = [ + { + publisher_id: 'pub123', + placement_id: '202', + }, + { + publisher_id: 'pub123', + placement_id: '203', + }, + { + publisher_id: 'pub124', + placement_id: '202', + }, + { + publisher_id: 'pub123', + placement_id: '203', + transactionId: '8df76688-1618-417a-87b1-60ad046841c9' + } + ].map(({publisher_id, placement_id, transactionId}) => { + return { + bidder: 'mediaforce', + params: {publisher_id, placement_id}, + mediaTypes: { + banner: { + sizes: [[300, 250], [600, 400]] + } + }, + transactionId: transactionId || 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }); + const refererInfo = { referer: 'https://www.prebid.org', reachedTop: true, @@ -95,7 +142,9 @@ describe('mediaforce bid adapter', function () { const requestUrl = `${baseUrl}/header_bid`; const dnt = utils.getDNT() ? 1 : 0; - const secure = 1 + const secure = window.location.protocol === 'https:' ? 1 : 0; + const pageUrl = window.location.href; + const timeout = 1500; it('should return undefined if no validBidRequests passed', function () { assert.equal(spec.buildRequests([]), undefined); @@ -108,21 +157,32 @@ 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 = {bids: bidRequests, refererInfo: refererInfo}; + let bidderRequest = { + bids: bidRequests, + refererInfo: refererInfo, + timeout: timeout, + auctionId: '210a474e-88f0-4646-837f-4253b7cf14fb' + }; let [request] = spec.buildRequests(bidRequests, bidderRequest); let data = JSON.parse(request.data); assert.deepEqual(data, { - id: bid.transactionId, + id: data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, site: { id: bid.params.publisher_id, publisher: {id: bid.params.publisher_id}, ref: encodeURIComponent(refererInfo.referer), - page: encodeURIComponent(refererInfo.referer), + page: pageUrl, }, device: { ua: navigator.userAgent, @@ -134,14 +194,32 @@ describe('mediaforce bid adapter', function () { tagid: bid.params.placement_id, secure: secure, bidfloor: bid.params.bidfloor, + ext: { + mediaforce: { + transactionId: bid.transactionId + } + }, banner: {w: 300, h: 250}, + native: { + ver: '1.2', + request: { + assets: [ + {id: 1, title: {len: 800}, required: 1}, + {id: 3, img: {w: 300, h: 250, type: 3}, required: 1}, + {id: 5, data: {type: 1}, required: 1} + ], + context: 1, + plcmttype: 1, + ver: '1.2' + } + }, }], }); assert.deepEqual(request, { method: 'POST', url: requestUrl, - data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250}}]}', + 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"}}}]}', }); }); @@ -157,6 +235,116 @@ describe('mediaforce bid adapter', function () { let data = JSON.parse(request.data); assert.deepEqual(data.imp[0].banner, {w: 300, h: 600, format: [{w: 300, h: 250}]}); }); + + it('should return proper requests for multiple imps', function () { + let bidderRequest = { + bids: multiBid, + refererInfo: refererInfo, + timeout: timeout, + auctionId: '210a474e-88f0-4646-837f-4253b7cf14fb' + }; + + let requests = spec.buildRequests(multiBid, bidderRequest); + assert.equal(requests.length, 2); + requests.forEach((req) => { + req.data = JSON.parse(req.data); + }); + + assert.deepEqual(requests, [ + { + method: 'POST', + url: requestUrl, + data: { + id: requests[0].data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, + site: { + id: 'pub123', + publisher: {id: 'pub123'}, + ref: encodeURIComponent(refererInfo.referer), + page: pageUrl, + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: '202', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }, { + tagid: '203', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }, { + tagid: '203', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: '8df76688-1618-417a-87b1-60ad046841c9' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }] + } + }, + { + method: 'POST', + url: requestUrl, + data: { + id: requests[1].data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, + site: { + id: 'pub124', + publisher: {id: 'pub124'}, + ref: encodeURIComponent(refererInfo.referer), + page: pageUrl, + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: '202', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }] + } + } + ]); + }); }); describe('interpretResponse() banner', function () { @@ -173,6 +361,7 @@ describe('mediaforce bid adapter', function () { cid: '2_ssl', h: 100, cat: ['IAB1-1'], + dealid: '3901521', crid: '2_ssl', impid: '2b3c9d103723a7', adid: '2_ssl', @@ -193,18 +382,191 @@ describe('mediaforce bid adapter', function () { assert.deepEqual(bids, ([{ ad: bid.adm, cpm: bid.price, + dealId: bid.dealid, creativeId: bid.adid, currency: response.body.cur, height: bid.h, netRevenue: true, burl: bid.burl, + mediaType: BANNER, requestId: bid.impid, ttl: 300, + meta: { advertiserDomains: [] }, width: bid.w, }])); }); }); + describe('interpretResponse() native as object', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + ext: { + advertiser_name: 'MediaForce', + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + }, + language: 'en', + agency_name: 'MediaForce DSP' + } + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + meta: { advertiserDomains: [] }, + }])); + }); + }); + + describe('interpretResponse() native as string', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let adm = JSON.stringify({ + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + } + }); + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + adm: adm + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + meta: { advertiserDomains: [] }, + }])); + }); + }); + describe('onBidWon()', function () { beforeEach(function() { sinon.stub(utils, 'triggerPixel'); diff --git a/test/spec/modules/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js new file mode 100644 index 00000000000..040c0abd566 --- /dev/null +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -0,0 +1,538 @@ +import { expect } from 'chai'; +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'; + +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 bidderRequest = { + bidderCode: 'mediakeys', + auctionId: '84212956-c377-40e8-b000-9885a06dc692', + bidderRequestId: '1c1b642f803242', + bids: [ + { + 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' } } + } + }, + ], + 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); + + bidRequests[0].mediaTypes.video = { + playerSize: [300, 250], + context: 'outstream' + } + + bidRequests[0].mediaTypes.native = { + type: 'image' + } + + 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.not.exist; + expect(data.imp[0].native).to.not.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'); + }); + + 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 and set floor by mediatype', function() { + const bidWithPriceFloors = utils.deepClone(bid); + + bidWithPriceFloors.mediaTypes.video = { + playerSize: [600, 480] + }; + + 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].bidfloor).to.equal(1); + + // expect(data.imp[1].video).to.exist; + // expect(data.imp[1].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' + }, + device: { + geo: { + country: 'FR', + city: 'Marseille' + } + } + } + }; + 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.device.geo.country).to.equal('FR'); + expect(data.device.geo.city).to.equal('Marseille'); + }); + }); + + 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(); + spec.interpretResponse(rawServerResponse, request); + utilsMock.expects('logError').once(); + 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('Build video response', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const rawServerResponseVideo = utils.deepClone(rawServerResponse); + rawServerResponseVideo.body.seatbid[0].bid[0].ext.prebid.type = 'V'; + 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'); + }); + + it('Build native response', function () { + const bidRequests = [utils.deepClone(bid)]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const rawServerResponseVideo = utils.deepClone(rawServerResponse); + rawServerResponseVideo.body.seatbid[0].bid[0].ext.prebid.type = 'N'; + const response = spec.interpretResponse(rawServerResponseVideo, request); + + expect(response.length).to.equal(1); + expect(response[0].mediaType).to.equal('native'); + expect(response[0].meta.mediaType).to.equal('native'); + }); + }); + + 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 dcec1050652..41a6338225e 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -9,18 +9,32 @@ const { } = CONSTANTS; const MOCK = { - AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739}, + 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'}}], + 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}, - 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', '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'}]}, + 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}, + 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'}}, + NO_BID_SET_TARGETING: {'div-gpt-ad-1460505748561-0': {}}, BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', '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', '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'}]}, NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}] } +function performAuctionWithFloorConfig() { + events.emit(AUCTION_INIT, {...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); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + function performStandardAuctionWithWinner() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...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); @@ -28,20 +42,28 @@ function performStandardAuctionWithWinner() { events.emit(BID_WON, MOCK.BID_WON); } +function performMultiFormatAuctionWithNoBid() { + events.emit(AUCTION_INIT, {...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 performStandardAuctionWithNoBid() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...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); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } function performStandardAuctionWithTimeout() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...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.SET_TARGETING); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } function getQueryData(url) { @@ -104,8 +126,10 @@ describe('Media.net Analytics Adapter', function() { cid: 'test123' } }); + medianetAnalytics.clearlogsQueue(); }); afterEach(function () { + medianetAnalytics.clearlogsQueue(); medianetAnalytics.disableAnalytics(); }); @@ -115,6 +139,16 @@ describe('Media.net Analytics Adapter', function() { expect(medianetAnalytics.getlogsQueue().length).to.equal(0); }); + it('should have all applicable sizes in request', function() { + medianetAnalytics.clearlogsQueue(); + performMultiFormatAuctionWithNoBid(); + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + medianetAnalytics.clearlogsQueue(); + expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); + expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); + expect(noBidLog.vplcmtt).to.equal('instream'); + }); + it('should have winner log in standard auction', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); @@ -137,12 +171,30 @@ describe('Media.net Analytics Adapter', function() { src: 'client', size: '300x250', mtype: 'banner', + gdpr: '0', cid: 'test123', lper: '1', ogbdp: '1.1495', flt: '1', supcrid: 'div-gpt-ad-1460505748561-0', - mpvid: '123' + mpvid: '123', + bidflr: '1.1' + }); + }); + + it('should have correct bid floor data in winner log', function() { + medianetAnalytics.clearlogsQueue(); + performAuctionWithFloorConfig(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog[0]).to.include({ + winner: '1', + curr: 'USD', + ogbdp: '1.1495', + bidflr: '1.1', + flrrule: 'banner', + flrdata: encodeURIComponent('ln=||skp=||enfj=true||enfd=||sr=||fs=') }); }); @@ -158,7 +210,7 @@ describe('Media.net Analytics Adapter', function() { expect(noBidLog.status).to.have.ordered.members(['1', '2']); expect(noBidLog.src).to.have.ordered.members(['client', 'client']); expect(noBidLog.curr).to.have.ordered.members(['', '']); - expect(noBidLog.mtype).to.have.ordered.members(['', '']); + expect(noBidLog.mtype).to.have.ordered.members(['banner', 'banner']); expect(noBidLog.ogbdp).to.have.ordered.members(['', '']); expect(noBidLog.mpvid).to.have.ordered.members(['', '']); expect(noBidLog.crid).to.have.ordered.members(['', '451466393']); @@ -176,7 +228,7 @@ describe('Media.net Analytics Adapter', function() { expect(timeoutLog.status).to.have.ordered.members(['1', '3']); expect(timeoutLog.src).to.have.ordered.members(['client', 'client']); expect(timeoutLog.curr).to.have.ordered.members(['', '']); - expect(timeoutLog.mtype).to.have.ordered.members(['', '']); + expect(timeoutLog.mtype).to.have.ordered.members(['banner', 'banner']); expect(timeoutLog.ogbdp).to.have.ordered.members(['', '']); expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index fcfdcdf8c2f..adb9663ba7c 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,7 +1,9 @@ import {expect} from 'chai'; import {spec} from 'modules/medianetBidAdapter.js'; +import { makeSlot } from '../integration/faker/googletag.js'; import { config } from 'src/config.js'; +$$PREBID_GLOBAL$$.version = $$PREBID_GLOBAL$$.version || 'version'; let VALID_BID_REQUEST = [{ 'bidder': 'medianet', 'params': { @@ -37,6 +39,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -81,6 +88,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -127,6 +139,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -731,6 +748,28 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + + VALID_VIDEO_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'video': { + 'skipppable': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'video': { + 'context': 'instream', + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], + VALID_PAYLOAD_PAGE_META = (() => { let PAGE_META; try { @@ -913,6 +952,42 @@ let VALID_BID_REQUEST = [{ } } }, + SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID = { + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'cpm': 12.00, + 'width': 640, + 'height': 480, + 'ttl': 180, + 'creativeId': '370637746', + 'netRevenue': true, + 'vastXml': '', + 'currency': 'USD', + 'dfp_id': 'video1', + 'mediaType': 'video', + 'vto': 5000, + 'mavtr': 10, + 'avp': true, + 'ap': true, + 'pl': true, + 'mt': true, + 'jslt': 3000, + 'context': 'outstream' + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, SERVER_VALID_BIDS = [{ 'no_bid': false, 'requestId': '27210feac00e96', @@ -1156,6 +1231,11 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_NATIVE); }); + it('should parse params for video request', function () { + let bidReq = spec.buildRequests(VALID_VIDEO_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.stringify(bidReq.data)).to.include('instream'); + }); + it('should have valid crid present in bid request', function() { sandbox.stub(config, 'getConfig').callsFake((key) => { const config = { @@ -1264,6 +1344,30 @@ describe('Media.net bid adapter', function () { expect(data.imp[1].ext).to.not.have.ownPropertyDescriptor('viewability'); expect(data.imp[1].ext.visibility).to.equal(0); }); + it('slot visibility should be calculable even in case of adUnitPath', function () { + const code = '/19968336/header-bid-tag-0'; + const divId = 'div-gpt-ad-1460505748561-0'; + window.googletag.pubads().setSlots([makeSlot({ code, divId })]); + + let boundingRect = { + top: 1010, + left: 1010, + bottom: 1050, + right: 1050 + }; + documentStub.withArgs(divId).returns({ + getBoundingClientRect: () => boundingRect + }); + documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ + getBoundingClientRect: () => boundingRect + }); + + const bidRequest = [{...VALID_BID_REQUEST[0], adUnitCode: code}] + const bidReq = spec.buildRequests(bidRequest, VALID_AUCTIONDATA); + const data = JSON.parse(bidReq.data); + expect(data.imp[0].ext.visibility).to.equal(2); + expect(data.imp[0].ext.viewability).to.equal(0); + }); }); describe('getUserSyncs', function () { @@ -1337,4 +1441,50 @@ describe('Media.net bid adapter', function () { expect(response).to.deep.equal(undefined); }); }); + + it('context should be outstream', 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 351fbb40228..f3f09a8ddf8 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -24,6 +24,43 @@ describe('MediaSquare bid adapter tests', function () { code: 'publishername_atf_desktop_rg_pave' }, }]; + var VIDEO_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + }]; + var NATIVE_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + }]; var BID_RESPONSE = {'body': { 'responses': [{ @@ -39,6 +76,7 @@ describe('MediaSquare bid adapter tests', function () { 'bidder': 'msqClassic', 'code': 'test/publishername_atf_desktop_rg_pave', 'bid_id': 'aaaa1234', + 'adomain': ['test.com'], }], }}; @@ -53,7 +91,7 @@ describe('MediaSquare bid adapter tests', function () { canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' }, uspConsent: '111222333', - userId: {'id5id': '1111'}, + userId: { 'id5id': { uid: '1111' } }, schain: { 'ver': '1.0', 'complete': 1, @@ -98,8 +136,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'); }); @@ -128,4 +176,33 @@ describe('MediaSquare bid adapter tests', function () { 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.property('type').and.to.equal('iframe'); + }); + 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'); + var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); + it('Verifies native in bid response', function () { + const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].native = {'title': 'native title'}; + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid).to.have.property('native'); + delete BID_RESPONSE.body.responses[0].native; + }); + it('Verifies video in bid response', function () { + const request = spec.buildRequests(VIDEO_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].video = {'xml': 'my vast XML', 'url': 'my vast url'}; + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid).to.have.property('vastXml'); + expect(bid).to.have.property('vastUrl'); + delete BID_RESPONSE.body.responses[0].video; + }); }); 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/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 new file mode 100644 index 00000000000..e849392ee4b --- /dev/null +++ b/test/spec/modules/multibid_spec.js @@ -0,0 +1,845 @@ +import {expect} from 'chai'; +import { + validateMultibid, + adjustBidderRequestsHook, + addBidResponseHook, + resetMultibidUnits, + sortByMultibid, + targetBidPoolHook, + resetMultiConfig +} from 'modules/multibid/index.js'; +import {parse as parseQuery} from 'querystring'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; + +describe('multibid adapter', function () { + let bidArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 75, + 'originalCpm': 75, + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA', + }]; + let bidCacheArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 66, + 'originalCpm': 66, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 38, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 38, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }]; + let bidArrayAlt = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 29, + 'originalCpm': 29, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderB', + 'cpm': 3, + 'requestId': '7g8h5j45l7654i', + 'originalCpm': 3, + 'bidder': 'bidderB' + }, { + 'bidderCode': 'bidderC', + 'cpm': 12, + 'requestId': '9d7f4h56t6483u', + 'originalCpm': 12, + 'bidder': 'bidderC' + }]; + let bidderRequests = [{ + 'bidderCode': 'bidderA', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderA', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }, { + 'bidderCode': 'bidderB', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderB', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }]; + + afterEach(function () { + config.resetConfig(); + resetMultiConfig(); + resetMultibidUnits(); + }); + + describe('adjustBidderRequestsHook', function () { + let result; + let callbackFn = function (bidderRequests) { + result = bidderRequests; + }; + + beforeEach(function() { + result = null; + }); + + it('does not modify bidderRequest when no multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + adjustBidderRequestsHook(callbackFn, bidRequests); + + expect(result).to.not.equal(null); + expect(result).to.deep.equal(bidRequests); + }); + + it('does modify bidderRequest when multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does modify bidderRequest when multibid config exists using bidders array', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidders: ['bidderA'], maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does only modifies bidderRequest when multibid config exists for bidder', function () { + let bidRequests = [{...bidderRequests[0]}, {...bidderRequests[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}, {...bidderRequests[1]}]); + + expect(result).to.not.equal(null); + expect(result[0]).to.not.deep.equal(bidRequests[0]); + expect(result[0].bidLimit).to.equal(2); + expect(result[1]).to.deep.equal(bidRequests[1]); + expect(result[1].bidLimit).to.equal(undefined); + }); + }); + + describe('addBidResponseHook', function () { + let result; + let callbackFn = function (adUnitCode, bid) { + result = { + 'adUnitCode': adUnitCode, + 'bid': bid + }; + }; + + beforeEach(function() { + result = null; + }); + + it('adds original bids and does not modify', function () { + 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.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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 bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + + delete bids[1].requestId; + delete result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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 bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderB', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderB', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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 bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('if no prefix in multibid configuration, modifies and returns bids under limit without preifx property', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].originalBidder = 'bidderA'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('does not include extra bids if cpm is less than floor value', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 65, + floorValue: 65, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.equal(null); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[3]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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 bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 25, + floorValue: 25, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + 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'); + }); + }); + + describe('targetBidPoolHook', function () { + let result; + let bidResult; + let callbackFn = function (bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + result = { + 'bidsReceived': bidsReceived, + 'adUnitBidLimit': adUnitBidLimit, + 'hasModified': hasModified + }; + }; + let bidResponseCallback = function (adUnitCode, bid) { + bidResult = bid; + }; + + beforeEach(function() { + result = null; + bidResult = null; + }); + + it('it does not run filter on bidsReceived if no multibid configuration found', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(false); + }); + + it('it does filter on bidsReceived if multibid configuration found with no prefix', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + bids.pop(); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(1); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + 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}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal([modifiedBids[1], modifiedBids[0]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + 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}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[0], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[0].cpm).to.equal(52); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].cpm).to.equal(29); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidder).to.equal('bidderC'); + expect(result.bidsReceived[2].cpm).to.equal(12); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.bidsReceived[3].bidder).to.equal('bidderB'); + expect(result.bidsReceived[3].cpm).to.equal(3); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + 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}); + + return bidResult; + }); + + config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm, 3); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(3); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(3); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it should collect all bids from auction and bid cache then sort and filter', 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}); + + return bidResult; + }); + + let bidPool = [].concat.apply(modifiedBids, [{...bidCacheArray[0]}, {...bidCacheArray[1]}]); + + expect(bidPool.length).to.equal(6); + + targetBidPoolHook(callbackFn, bidPool, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([bidPool[4], bidPool[3], bidPool[2], bidPool[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + }); + + describe('validate multibid', function () { + it('should fail validation for missing bidder name in entry', function () { + let conf = [{maxBids: 1}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should pass validation on all multibid entries', function () { + let conf = [{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(true); + }); + + it('should fail validation for maxbids less than 1 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should fail validation for maxbids greater than 9 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 10}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should add multbid entries to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 1}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + + it('should modify multbid entries and add to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 9}]); + }); + + it('should filter multbid entry and add modified to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf.length).to.equal(1); + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + }); + + describe('sort multibid', function () { + it('should not alter order', function () { + let bids = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + + it('should sort dynamic alias bidders to end', function () { + let bids = [{ + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + }); +}); diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js deleted file mode 100644 index ea998303fe3..00000000000 --- a/test/spec/modules/mytargetBidAdapter_spec.js +++ /dev/null @@ -1,229 +0,0 @@ -import { expect } from 'chai'; -import { config } from 'src/config.js'; -import { spec } from 'modules/mytargetBidAdapter.js'; - -describe('MyTarget Adapter', function() { - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - let validBid = { - bidder: 'mytarget', - params: { - placementId: '1' - } - }; - - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - - it('should return false for when required params are not passed', function () { - let invalidBid = { - bidder: 'mytarget', - params: {} - }; - - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - bidId: 'bid1', - bidder: 'mytarget', - params: { - placementId: '1' - } - }, - { - bidId: 'bid2', - bidder: 'mytarget', - params: { - placementId: '2', - position: 1, - response: 1, - bidfloor: 10000 - } - } - ]; - let bidderRequest = { - refererInfo: { - referer: 'https://example.com?param=value' - } - }; - - let bidRequest = spec.buildRequests(bidRequests, bidderRequest); - - 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.data).to.be.an('object'); - expect(bidRequest.data.places).to.be.an('array'); - expect(bidRequest.data.places).to.have.lengthOf(2); - }); - - it('should pass bid parameters', function() { - let place1 = bidRequest.data.places[0]; - let place2 = bidRequest.data.places[1]; - - expect(place1.placementId).to.equal('1'); - expect(place2.placementId).to.equal('2'); - expect(place1.id).to.equal('bid1'); - expect(place2.id).to.equal('bid2'); - }); - - it('should pass default position and response type', function() { - let place = bidRequest.data.places[0]; - - expect(place.position).to.equal(0); - expect(place.response).to.equal(0); - }); - - it('should pass provided position and response type', function() { - let place = bidRequest.data.places[1]; - - expect(place.position).to.equal(1); - expect(place.response).to.equal(1); - }); - - it('should not pass default bidfloor', function() { - let place = bidRequest.data.places[0]; - - expect(place.bidfloor).not.to.exist; - }); - - it('should not pass provided bidfloor', function() { - let place = bidRequest.data.places[1]; - - expect(place.bidfloor).to.exist; - expect(place.bidfloor).to.equal(10000); - }); - - it('should pass site parameters', function() { - let site = bidRequest.data.site; - - expect(site).to.be.an('object'); - expect(site.sitename).to.equal('example.com'); - expect(site.page).to.equal('https://example.com?param=value'); - }); - - it('should pass settings', function() { - let settings = bidRequest.data.settings; - - expect(settings).to.be.an('object'); - expect(settings.currency).to.equal('RUB'); - 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); - }); - - 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 - } - ], - 'bids': - [ - { - 'displayUrl': 'https://ad.mail.ru/hbid_imp/12345', - 'size': - { - 'height': '400', - 'width': '240' - }, - 'id': '1', - 'currency': 'RUB', - 'price': 100, - 'ttl': 360, - 'creativeId': '123456' - }, - { - 'adm': '

Ad

', - 'size': - { - 'height': '250', - 'width': '300' - }, - 'id': '2', - 'price': 200 - } - ] - } - }; - - let bids = spec.interpretResponse(serverResponse); - - it('should return empty array for response with no bids', function() { - let emptyBids = spec.interpretResponse({ body: {} }); - - expect(emptyBids).to.have.lengthOf(0); - }); - - it('should parse all bids from response', function() { - expect(bids).to.have.lengthOf(2); - }); - - it('should parse bid with ad url', function() { - expect(bids[0].requestId).to.equal('1'); - expect(bids[0].cpm).to.equal(100); - expect(bids[0].width).to.equal('240'); - expect(bids[0].height).to.equal('400'); - expect(bids[0].ttl).to.equal(360); - expect(bids[0].currency).to.equal('RUB'); - expect(bids[0]).to.have.property('creativeId'); - expect(bids[0].creativeId).to.equal('123456'); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adUrl).to.equal('https://ad.mail.ru/hbid_imp/12345'); - expect(bids[0]).to.not.have.property('ad'); - }); - - it('should parse bid with ad markup', function() { - expect(bids[1].requestId).to.equal('2'); - expect(bids[1].cpm).to.equal(200); - expect(bids[1].width).to.equal('300'); - expect(bids[1].height).to.equal('250'); - expect(bids[1].ttl).to.equal(180); - expect(bids[1].currency).to.equal('RUB'); - expect(bids[1]).to.have.property('creativeId'); - expect(bids[1].creativeId).not.to.equal('123456'); - expect(bids[1].netRevenue).to.equal(true); - expect(bids[1].ad).to.equal('

Ad

'); - expect(bids[1]).to.not.have.property('adUrl'); - }); - }); -}); 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 new file mode 100644 index 00000000000..4202b7c6f91 --- /dev/null +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -0,0 +1,234 @@ +import { expect } from 'chai' +import { spec } from 'modules/nativoBidAdapter.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' + +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', + } + + 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 bid2 = Object.assign({}, bid) + delete bid2.params + bid2.params = {} + expect(spec.isBidRequestValid(bid2)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + it('url should contain query string parameters', function () { + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + + expect(request.url).to.exist + expect(request.url).to.be.a('string') + + expect(request.url).to.include('?') + 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') + }) + }) +}) + +describe('interpretResponse', function () { + let response = { + id: '126456', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1F254428-AB11-4D5E-9887-567B3F952CA5', + cpm: 3.569, + currency: 'USD', + width: 300, + height: 250, + creativeId: '1060_72_6760217', + dealId: 'f70362ac-f3cf-4225-82a5-948b690927a6', + netRevenue: true, + ttl: 360, + ad: '', + meta: { + advertiserDomains: ['test.com'], + }, + }, + ] + + let bidderRequest = { + id: 123456, + bids: [ + { + params: { + placementId: 1, + }, + }, + ], + } + + // mock + spec.getRequestId = () => 123456 + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(Object.keys(result[0])).to.have.deep.members( + Object.keys(expectedResponse[0]) + ) + }) + + it('handles nobid responses', function () { + let response = {} + let bidderRequest + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(result.length).to.equal(0) + }) +}) + +describe('getUserSyncs', function () { + const response = [ + { + body: { + cur: 'USD', + id: 'a136dbd8-4387-48bf-b8e4-ff9c1d6056ee', + seatbid: [ + { + bid: [{}], + seat: 'seat_0', + syncUrls: [ + { + type: 'image', + url: 'pixel-tracker-test-url/?{GDPR_params}', + }, + { + type: 'iframe', + url: 'iframe-tracker-test-url/?{GDPR_params}', + }, + ], + }, + ], + }, + }, + ] + + const gdprConsent = { + gdprApplies: true, + consentString: '111111', + } + + const uspConsent = { + uspConsent: '1YYY', + } + + it('Returns empty array if no supported user syncs', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(0) + }) + + it('Returns valid iframe user sync', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + 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.contain( + 'gdpr=1&gdpr_consent=111111&us_privacy=1YYY' + ) + }) + + it('Returns valid URL and type', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + response, + gdprConsent, + uspConsent + ) + 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.contain( + 'gdpr=1&gdpr_consent=111111&us_privacy=1YYY' + ) + }) +}) 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..15256d6c382 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -2,90 +2,55 @@ 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]] - } - ] - }; - let request = []; - - it('validate_pub_params', function() { - expect( - spec.isBidRequestValid({ - bidder: 'nextMillennium', - params: { - placement_id: 12345 - } - }) - ).to.equal(true); - }); + const bidRequestData = [ + { + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '-1' }, + sizes: [[300, 250]] + } + ]; 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'); - }); - - it('validate_getUserSyncs_function', function() { - expect(spec.getUserSyncs({ iframeEnabled: true })).to.have.lengthOf(1); - expect(spec.getUserSyncs({ iframeEnabled: false })).to.have.lengthOf(0); - - 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'); + expect(JSON.parse(request[0].data).id).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); }); 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 deleted file mode 100644 index 7722443e584..00000000000 --- a/test/spec/modules/nextrollBidAdapter_spec.js +++ /dev/null @@ -1,268 +0,0 @@ -import { expect } from 'chai'; -import { spec, tryGetPubtag, hasCCPAConsent } from 'modules/nextrollBidAdapter.js'; -import * as utils from 'src/utils.js'; - -describe('nextrollBidAdapter', function() { - let utilsMock; - beforeEach(function () { - utilsMock = sinon.mock(utils); - }); - - afterEach(function() { - global.NextRoll = undefined; - utilsMock.restore(); - }); - - let validBid = { - bidder: 'nextroll', - adUnitCode: 'adunit-code', - bidId: 'bid_id', - sizes: [[300, 200]], - params: { - bidfloor: 1, - zoneId: 'zone1', - publisherId: 'publisher_id' - } - }; - let bidWithoutValidId = { id: '' }; - let bidWithoutId = { params: { zoneId: 'zone1' } }; - - describe('nativeBidRequest', () => { - it('validates native spec', () => { - let nativeAdUnit = [{ - bidder: 'nextroll', - adUnitCode: 'adunit-code', - bidId: 'bid_id', - mediaTypes: { - native: { - title: {required: true, len: 80}, - image: {required: true, sizes: [728, 90]}, - sponsoredBy: {required: false, len: 20}, - clickUrl: {required: true}, - body: {required: true, len: 25}, - icon: {required: true, sizes: [50, 50], aspect_ratios: [{ratio_height: 3, ratio_width: 4}]}, - someRandomAsset: {required: false, len: 100} // This should be ignored - } - }, - params: { - bidfloor: 1, - zoneId: 'zone1', - publisherId: 'publisher_id' - } - }]; - - let request = spec.buildRequests(nativeAdUnit) - let assets = request[0].data.imp.native.request.native.assets - - let excptedAssets = [ - {id: 1, required: 1, title: {len: 80}}, - {id: 2, required: 1, img: {w: 728, h: 90, wmin: 1, hmin: 1, type: 3}}, - {id: 3, required: 1, img: {w: 50, h: 50, wmin: 4, hmin: 3, type: 1}}, - {id: 5, required: 0, data: {len: 20, type: 1}}, - {id: 6, required: 1, data: {len: 25, type: 2}} - ] - expect(assets).to.be.deep.equal(excptedAssets) - }) - }) - - describe('isBidRequestValid', function() { - it('validates the bids correctly when the bid has an id', function() { - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); - - it('validates the bids correcly when the bid does not have an id', function() { - expect(spec.isBidRequestValid(bidWithoutValidId)).to.be.false; - expect(spec.isBidRequestValid(bidWithoutId)).to.be.false; - }); - }); - - describe('buildRequests', function() { - it('builds the same amount of requests as valid requests it takes', function() { - expect(spec.buildRequests([validBid, validBid], {})).to.be.lengthOf(2); - }); - - it('doest not build a request when there is no valid requests', function () { - expect(spec.buildRequests([], {})).to.be.lengthOf(0); - }); - - it('builds a request with POST method', function () { - expect(spec.buildRequests([validBid], {})[0].method).to.equal('POST'); - }); - - it('builds a request with cookies method', function () { - expect(spec.buildRequests([validBid], {})[0].options.withCredentials).to.be.true; - }); - - it('builds a request with id, url and imp object', function () { - const request = spec.buildRequests([validBid], {})[0]; - expect(request.data.id).to.be.an('string').that.is.not.empty; - expect(request.url).to.equal('https://d.adroll.com/bid/prebid/'); - expect(request.data.imp).to.exist.and.to.be.a('object'); - }); - - it('builds a request with site and device information', function () { - const request = spec.buildRequests([validBid], {})[0]; - - expect(request.data.site).to.exist.and.to.be.a('object'); - expect(request.data.device).to.exist.and.to.be.a('object'); - }); - - it('builds a request with a complete imp object', function () { - const request = spec.buildRequests([validBid], {})[0]; - - expect(request.data.imp.id).to.equal('bid_id'); - expect(request.data.imp.bidfloor).to.be.equal(1); - expect(request.data.imp.banner).to.exist.and.to.be.a('object'); - expect(request.data.imp.ext.zone.id).to.be.equal('zone1'); - }); - - it('includes the sizes into the request correctly', function () { - const bannerObject = spec.buildRequests([validBid], {})[0].data.imp.banner; - - expect(bannerObject.format).to.exist; - expect(bannerObject.format).to.be.lengthOf(1); - expect(bannerObject.format[0].w).to.be.equal(300); - expect(bannerObject.format[0].h).to.be.equal(200); - }); - - it('sets the CCPA consent string', function () { - const us_privacy = '1YYY'; - const request = spec.buildRequests([validBid], {'uspConsent': us_privacy})[0]; - - expect(request.data.regs.ext.us_privacy).to.be.equal(us_privacy); - }); - }); - - describe('interpretResponse', function () { - let responseBody = { - id: 'bidresponse_id', - dealId: 'deal_id', - seatbid: [ - { - bid: [ - { - price: 1.2, - w: 300, - h: 200, - crid: 'crid1', - adm: 'adm1' - } - ] - }, - { - bid: [ - { - price: 2.1, - w: 250, - h: 300, - crid: 'crid2', - adm: 'adm2' - } - ] - } - ] - }; - - it('returns an empty list when there is no response body', function () { - expect(spec.interpretResponse({}, {})).to.be.eql([]); - }); - - it('builds the same amount of responses as server responses it receives', function () { - expect(spec.interpretResponse({body: responseBody}, {})).to.be.lengthOf(2); - }); - - it('builds a response with the expected fields', function () { - const response = spec.interpretResponse({body: responseBody}, {})[0]; - - expect(response.requestId).to.be.equal('bidresponse_id'); - expect(response.cpm).to.be.equal(1.2); - expect(response.width).to.be.equal(300); - expect(response.height).to.be.equal(200); - expect(response.creativeId).to.be.equal('crid1'); - expect(response.dealId).to.be.equal('deal_id'); - expect(response.currency).to.be.equal('USD'); - expect(response.netRevenue).to.be.equal(true); - expect(response.ttl).to.be.equal(300); - expect(response.ad).to.be.equal('adm1'); - }); - }); - - describe('interpret native response', () => { - let clickUrl = 'https://clickurl.com/with/some/path' - let titleText = 'Some title' - let imgW = 300 - let imgH = 250 - let imgUrl = 'https://clickurl.com/img.png' - let brandText = 'Some Brand' - let impUrl = 'https://clickurl.com/imptracker' - - let responseBody = { - body: { - id: 'bidresponse_id', - seatbid: [{ - bid: [{ - price: 1.2, - crid: 'crid1', - adm: { - link: {url: clickUrl}, - assets: [ - {id: 1, title: {text: titleText}}, - {id: 2, img: {w: imgW, h: imgH, url: imgUrl}}, - {id: 5, data: {value: brandText}} - ], - imptrackers: [impUrl] - } - }] - }] - } - }; - - it('Should interpret response', () => { - let response = spec.interpretResponse(utils.deepClone(responseBody)) - let expectedResponse = { - clickUrl: clickUrl, - impressionTrackers: [impUrl], - privacyLink: 'https://info.evidon.com/pub_info/573', - privacyIcon: 'https://c.betrad.com/pub/icon1.png', - title: titleText, - image: {url: imgUrl, width: imgW, height: imgH}, - sponsoredBy: brandText, - clickTrackers: [], - jstracker: [] - } - - expect(response[0].native).to.be.deep.equal(expectedResponse) - }) - - it('Should interpret all assets', () => { - let allAssetsResponse = utils.deepClone(responseBody) - let iconUrl = imgUrl + '?icon=true', iconW = 10, iconH = 15 - let logoUrl = imgUrl + '?logo=true', logoW = 20, logoH = 25 - let bodyText = 'Some body text' - - allAssetsResponse.body.seatbid[0].bid[0].adm.assets.push(...[ - {id: 3, img: {w: iconW, h: iconH, url: iconUrl}}, - {id: 4, img: {w: logoW, h: logoH, url: logoUrl}}, - {id: 6, data: {value: bodyText}} - ]) - - let response = spec.interpretResponse(allAssetsResponse) - let expectedResponse = { - clickUrl: clickUrl, - impressionTrackers: [impUrl], - jstracker: [], - clickTrackers: [], - privacyLink: 'https://info.evidon.com/pub_info/573', - privacyIcon: 'https://c.betrad.com/pub/icon1.png', - title: titleText, - image: {url: imgUrl, width: imgW, height: imgH}, - icon: {url: iconUrl, width: iconW, height: iconH}, - logo: {url: logoUrl, width: logoW, height: logoH}, - body: bodyText, - sponsoredBy: brandText - } - - expect(response[0].native).to.be.deep.equal(expectedResponse) - }) - }) -}); diff --git a/test/spec/modules/nextrollIdSystem_spec.js b/test/spec/modules/nextrollIdSystem_spec.js new file mode 100644 index 00000000000..d89c7fe3c98 --- /dev/null +++ b/test/spec/modules/nextrollIdSystem_spec.js @@ -0,0 +1,56 @@ +import { nextrollIdSubmodule, storage } from 'modules/nextrollIdSystem.js'; + +const LS_VALUE = `{ + "AdID":{"id":"adid","key":"AdID"}, + "AdID:1002": {"id":"adid","key":"AdID:1002","value":"id_value"}}`; + +describe('NextrollId module', function () { + let sandbox = sinon.sandbox.create(); + let hasLocalStorageStub; + let getLocalStorageStub; + + beforeEach(function() { + hasLocalStorageStub = sandbox.stub(storage, 'hasLocalStorage'); + getLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + sandbox.restore(); + }) + + const testCases = [ + { + expect: { + id: {nextrollId: 'id_value'}, + }, + params: {partnerId: '1002'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '1003'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: ''}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '102'}, + localStorage: undefined + }, + { + expect: {id: undefined}, + params: undefined, + localStorage: undefined + } + ] + testCases.forEach( + (testCase, i) => it(`getId() (TC #${i}) should return the nextroll id if it exists`, function () { + getLocalStorageStub.withArgs('dca0.com').returns(testCase.localStorage); + const id = nextrollIdSubmodule.getId({params: testCase.params}); + expect(id).to.be.deep.equal(testCase.expect); + })) +}); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 3b8fd32160b..f775e439a80 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -50,6 +50,142 @@ describe('Nobid Adapter', function () { }); }); + 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 +250,12 @@ 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; + 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 +339,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; @@ -228,6 +366,75 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsEIDs', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'userIdAsEids': [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'CRITEO_ID', + 'atype': 1 + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5_ID', + 'atype': 1 + } + ], + 'ext': { + 'linkType': 0 + } + }, + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'TD_ID', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ] + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + 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); + expect(payload.eids[0].source).to.exist.and.to.equal('criteo.com'); + expect(payload.eids[0].uids[0].id).to.exist.and.to.equal('CRITEO_ID'); + expect(payload.eids[1].source).to.exist.and.to.equal('id5-sync.com'); + expect(payload.eids[1].uids[0].id).to.exist.and.to.equal('ID5_ID'); + expect(payload.eids[2].source).to.exist.and.to.equal('adserver.org'); + expect(payload.eids[2].uids[0].id).to.exist.and.to.equal('TD_ID'); + }); + }); + describe('buildRequests', function () { const SITE_ID = 2; const REFERER = 'https://www.examplereferer.com'; @@ -264,6 +471,38 @@ describe('Nobid Adapter', function () { 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'); @@ -291,6 +530,43 @@ describe('Nobid Adapter', function () { 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('buildRequestsRefreshCount', function () { @@ -460,6 +736,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 new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).to.have.length(40); + }); + + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js new file mode 100644 index 00000000000..0a57690db27 --- /dev/null +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -0,0 +1,410 @@ +import { expect } from 'chai'; +import { spec } from 'modules/oguryBidAdapter'; +import { deepClone } from 'src/utils.js'; + +const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; + +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 = deepClone(bidRequests[0]); + + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('should not validate incorrect bid', () => { + let invalidBid = 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 = 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 = 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 = 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 an element of type image', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[0].url).to.contain('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch'); + }); + + it('should set the source as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(userSyncs[0].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}`); + }); + + it('should return an empty array when pixel is disable', () => { + syncOptions.pixelEnabled = false; + expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); + }); + }); + + describe('buildRequests', function () { + const defaultTimeout = 1000; + const expectedRequestObject = { + id: bidRequests[0].auctionId, + at: 2, + tmax: defaultTimeout, + imp: [{ + id: bidRequests[0].bidId, + tagid: bidRequests[0].params.adUnitId, + bidfloor: 4, + banner: { + format: [{ + w: 300, + h: 250 + }] + } + }, { + id: bidRequests[1].bidId, + tagid: bidRequests[1].params.adUnitId, + bidfloor: 0, + banner: { + format: [{ + w: 600, + h: 500 + }] + } + }], + regs: { + ext: { + gdpr: 1 + }, + }, + site: { + id: bidRequests[0].params.assetKey, + domain: window.location.hostname, + }, + user: { + ext: { + consent: bidderRequest.gdprConsent.consentString + }, + } + }; + + it('sends bid request to ENDPOINT via POST', function () { + const validBidRequests = deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.url).to.equal(BID_HOST); + expect(request.method).to.equal('POST'); + }); + + it('bid request object should be conform', function () { + const validBidRequests = 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: 1 + }, + }, + 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 = 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 = 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 = deepClone(expectedRequestObject) + expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor = 0; + let validBidRequests = 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 + }, { + 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 + }] + + 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 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') + }) + }) +}); diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index a91e9ac27e8..d6dacb44529 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -1,6 +1,5 @@ import { expect } from 'chai'; import { spec } from 'modules/oneVideoBidAdapter.js'; -import * as utils from 'src/utils.js'; describe('OneVideoBidAdapter', function () { let bidRequest; @@ -60,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 false when the "video" param is missing', function () { + 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 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' }; @@ -77,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' }; @@ -190,34 +237,50 @@ 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); }) }); describe('spec.buildRequests', function () { it('should create a POST request for every bid', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(spec.ENDPOINT + bidRequest.params.pubId); }); it('should attach the bid request object', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].bidRequest).to.equal(bidRequest); }); it('should attach request data', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - const [ width, height ] = bidRequest.sizes; + const [width, height] = bidRequest.sizes; const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; - const VERSION = '3.0.3'; + 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); @@ -231,8 +294,10 @@ describe('OneVideoBidAdapter', function () { it('must parse bid size from a nested array', function () { const width = 640; const height = 480; - bidRequest.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + bidRequest.sizes = [ + [width, height] + ]; + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); @@ -240,14 +305,14 @@ describe('OneVideoBidAdapter', function () { it('should set pubId to HBExchange when bid.params.video.e2etest = true', function () { bidRequest.params.video.e2etest = true; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(spec.E2ETESTENDPOINT + 'HBExchange'); }); it('should attach End 2 End test data', function () { bidRequest.params.video.e2etest = true; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].bidfloor).to.not.exist; expect(data.imp[0].video.w).to.equal(300); @@ -260,7 +325,7 @@ describe('OneVideoBidAdapter', function () { }); it('it should create new schain and send it if video.params.sid exists', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -281,7 +346,7 @@ describe('OneVideoBidAdapter', function () { }] }; bidRequest.schain = globalSchain; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -300,7 +365,7 @@ describe('OneVideoBidAdapter', function () { }] }; bidRequest.schain = globalSchain; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -310,49 +375,365 @@ describe('OneVideoBidAdapter', function () { }) it('should append hp to new schain created by sid if video.params.hp is passed', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes[0].hp).to.equal(bidRequest.params.video.hp); }) + it('should not accept key values pairs if custom is Undefined ', function () { + bidRequest.params.video.custom = null; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Array ', function () { + bidRequest.params.video.custom = []; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Number ', function () { + bidRequest.params.video.custom = 123456; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is String ', function () { + bidRequest.params.video.custom = 'keyValuePairs'; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Boolean ', function () { + bidRequest.params.video.custom = true; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should accept key values pairs if custom is Object ', function () { + bidRequest.params.video.custom = {}; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.a('object'); + }); + it('should accept key values pairs if custom is Object ', function () { + bidRequest.params.video.custom = { + key1: 'value1', + key2: 'value2', + key3: 4444444, + key4: false, + key5: { + nested: 'object' + }, + key6: ['string', 2, true, null], + key7: null, + key8: undefined + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const custom = requests[0].data.imp[0].ext.custom; + expect(custom['key1']).to.be.a('string'); + expect(custom['key2']).to.be.a('string'); + expect(custom['key3']).to.be.a('number'); + expect(custom['key4']).to.not.exist; + expect(custom['key5']).to.not.exist; + expect(custom['key6']).to.not.exist; + expect(custom['key7']).to.not.exist; + expect(custom['key8']).to.not.exist; + }); + + 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + + 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 = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + + it('should load banner instead of video floor when DAP is active bid.params.video.display = 1', function () { + bidRequest.params.video.display = 1; + 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 = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(2.22); + }) + + 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 = requests[0].data; + expect(data.cur).is.a('string'); + expect(data.cur).to.equal('EUR'); + 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 }); + 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 }); + 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 }); + 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 = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + crid: 2, + price: 6.01, + adm: '', + adomain: [ + 'verizonmedia.com' + ], + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); let o = { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: serverResponse.seatbid[0].bid[0].price, - adId: serverResponse.seatbid[0].bid[0].adid, creativeId: serverResponse.seatbid[0].bid[0].crid, vastXml: serverResponse.seatbid[0].bid[0].adm, width: 640, height: 480, mediaType: 'video', currency: 'USD', - ttl: 100, + ttl: 300, 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); }); @@ -370,12 +751,51 @@ describe('OneVideoBidAdapter', function () { } } } - const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '
DAP UNIT HERE
'}]}], cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + crid: 2, + price: 6.01, + adm: '
DAP UNIT HERE
' + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); expect(bidResponse.ad).to.equal('
DAP UNIT HERE
'); expect(bidResponse.mediaType).to.equal('banner'); expect(bidResponse.renderer).to.be.undefined; }); + + 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.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.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.ttl).to.equal(300); + }); + it('should use custom ttl if under 3600', function () { + bidRequest.params.video.ttl = 1000; + 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.ttl).to.equal(1000); + }); }); describe('when GDPR and uspConsent applies', function () { @@ -418,22 +838,22 @@ describe('OneVideoBidAdapter', function () { }); it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.gdpr).to.equal(1); }); it('should send the consent string', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); it('should send the uspConsent string', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); }); it('should send the uspConsent and GDPR ', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); }); @@ -477,7 +897,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -527,7 +947,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -573,7 +993,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -592,15 +1012,35 @@ describe('OneVideoBidAdapter', function () { const GDPR_CONSENT_STRING = 'GDPR_CONSENT_STRING'; it('should get correct user sync when iframeEnabled', function () { - let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, {gdprApplies: true, consentString: GDPR_CONSENT_STRING}) - expect(pixel[2].type).to.equal('image'); - expect(pixel[2].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING + '&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=1&gdpr_consent=' + encodeURI(GDPR_CONSENT_STRING)); + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, { + gdprApplies: true, + consentString: GDPR_CONSENT_STRING + }) + expect(pixel[1].type).to.equal('image'); + expect(pixel[1].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING + '&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=1&gdpr_consent=' + encodeURI(GDPR_CONSENT_STRING)); }); it('should default to gdprApplies=0 when consentData is undefined', function () { - let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, undefined); - expect(pixel[2].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=0&gdpr_consent=&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=0&gdpr_consent='); + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, undefined); + expect(pixel[1].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=0&gdpr_consent=&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=0&gdpr_consent='); }); }); + + describe('verify sync pixels', function () { + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, undefined); + it('should be UPS sync pixel for DBM', function () { + expect(pixel[0].url).to.equal('https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true') + }); + + it('should be TTD sync pixel', function () { + expect(pixel[2].url).to.equal('https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1') + }); + }) }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index a951c74b20b..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'); }); }); }); @@ -132,29 +159,54 @@ describe('onetag', function () { const data = JSON.parse(d); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'onetagSid'); + expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'timing', 'version'); expect(data.location).to.be.a('string'); - expect(data.masked).to.be.a('number'); - expect(data.referrer).to.be.a('string'); + expect(data.masked).to.be.oneOf([0, 1, 2]); + expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); expect(data.sHeight).to.be.a('number'); expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); 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'); expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); expect(data.bids).to.be.an('array'); + expect(data.version).to.have.all.keys('prebid', 'adapter'); const bids = data['bids']; 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'); @@ -239,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 () { @@ -322,6 +375,7 @@ function getBannerVideoResponse() { currency: 'USD', requestId: 'banner', mediaType: BANNER, + adomain: [] }, { cpm: 13, @@ -333,7 +387,8 @@ function getBannerVideoResponse() { requestId: 'videoInstream', vastUrl: 'https://videoinstream.org', videoCacheKey: 'key', - mediaType: VIDEO + mediaType: VIDEO, + adomain: ['test_domain'] }, { cpm: 13, @@ -346,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/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..f82f7856fb2 --- /dev/null +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -0,0 +1,793 @@ +import ooloAnalytics, { PAGEVIEW_ID } from 'modules/ooloAnalyticsAdapter.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import constants from 'src/constants.json' +import events from 'src/events' +import { config } from 'src/config'; +import { buildAuctionData, generatePageViewId } from 'modules/ooloAnalyticsAdapter'; + +const auctionId = '0ea14159-2058-4b87-a966-9d7652176a56'; +const auctionStart = 1598513385415 +const timeout = 3000 +const adUnit1 = 'top_1'; +const adUnit2 = 'top_2'; +const bidId1 = '392b5a6b05d648' +const bidId2 = '392b5a6b05d649' +const bidId3 = '392b5a6b05d650' + +const auctionInit = { + timestamp: auctionStart, + auctionId: auctionId, + timeout: timeout, + auctionStart, + adUnits: [{ + code: adUnit1, + transactionId: 'abalksdkjfh-12sade' + }, { + code: adUnit2, + transactionId: 'abalksdkjfh-12sadf' + }] +}; + +const bidRequested = { + auctionId, + bidderCode: 'appnexus', + bidderRequestId: '2946b569352ef2', + start: 1598513405254, + bids: [ + { + auctionId, + bidId: bidId1, + bidderRequestId: '2946b569352ef2', + bidder: 'appnexus', + adUnitCode: adUnit1, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + { + auctionId, + bidId: bidId2, + bidderRequestId: '2946b569352ef3', + bidder: 'rubicon', + adUnitCode: adUnit2, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + { + auctionId, + bidId: bidId3, + bidderRequestId: '2946b569352ef4', + bidder: 'ix', + adUnitCode: adUnit2, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + ], +} + +const noBid = { + auctionId, + adUnitCode: adUnit2, + bidId: bidId2, + requestId: bidId2 +} + +const bidResponse = { + auctionId, + bidderCode: 'appnexus', + mediaType: 'banner', + width: 0, + height: 0, + statusMessage: 'Bid available', + adId: '222bb26f9e8bd', + cpm: 0.112256, + responseTimestamp: 1598513485254, + requestTimestamp: 1462919238936, + bidder: 'appnexus', + adUnitCode: adUnit1, + timeToRespond: 401, + pbLg: '0.00', + pbMg: '0.10', + pbHg: '0.11', + pbAg: '0.10', + size: '0x0', + requestId: bidId1, + creativeId: '123456', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '222bb26f9e8bd', + hb_pb: '10.00', + hb_size: '0x0', + foobar: '0x0', + }, + netRevenue: true, + currency: 'USD', + ttl: 300, +} + +const auctionEnd = { + auctionId: auctionId +}; + +const bidTimeout = [ + { + adUnitCode: adUnit2, + auctionId: auctionId, + bidId: bidId3, + bidder: 'ix', + timeout: timeout + } +]; + +const bidWon = { + auctionId, + adUnitCode: adUnit1, + bidId: bidId1, + cpm: 0.5 +} + +function simulateAuction () { + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(constants.EVENTS.NO_BID, noBid); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); +} + +describe('oolo Prebid Analytic', () => { + let clock + + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers() + }); + + afterEach(() => { + ooloAnalytics.disableAnalytics(); + events.getEvents.restore() + clock.restore(); + }) + + describe('enableAnalytics init options', () => { + it('should not enable analytics if invalid config', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: undefined + } + }) + + expect(server.requests).to.have.length(0) + }) + + it('should send prebid config to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + const conf = {} + const pbjsConfig = config.getConfig() + + Object.keys(pbjsConfig).forEach(key => { + if (key[0] !== '_') { + conf[key] = pbjsConfig[key] + } + }) + + expect(server.requests[1].url).to.contain('/hbconf') + expect(JSON.parse(server.requests[1].requestBody)).to.deep.equal(conf) + }) + + it('should request server config and send page data', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + expect(server.requests[0].url).to.contain('?pid=123') + + const pageData = JSON.parse(server.requests[2].requestBody) + + expect(pageData).to.have.property('timestamp') + expect(pageData).to.have.property('screenWidth') + expect(pageData).to.have.property('screenHeight') + expect(pageData).to.have.property('url') + expect(pageData).to.have.property('protocol') + expect(pageData).to.have.property('origin') + expect(pageData).to.have.property('referrer') + expect(pageData).to.have.property('pbVersion') + expect(pageData).to.have.property('pvid') + expect(pageData).to.have.property('pid') + expect(pageData).to.have.property('pbModuleVersion') + expect(pageData).to.have.property('domContentLoadTime') + expect(pageData).to.have.property('pageLoadTime') + }) + }) + + describe('data handling and sending events', () => { + it('should send an "auction" event to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(500) + + simulateAuction() + + expect(server.requests.length).to.equal(3) + clock.tick(2000) + expect(server.requests.length).to.equal(4) + + const request = JSON.parse(server.requests[3].requestBody); + + expect(request).to.include({ + eventType: 'auction', + pid: 123, + auctionId, + auctionStart, + auctionEnd: 0, + timeout, + }) + expect(request.pvid).to.be.a('number') + expect(request.adUnits).to.have.length(2) + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.auctionId).to.equal(auctionId) + expect(auctionAdUnit1.bids).to.be.an('array') + expect(auctionAdUnit1.bids).to.have.length(1) + + // bid response + expect(auctionAdUnit1.bids[0].bidId).to.equal(bidId1) + expect(auctionAdUnit1.bids[0].bst).to.equal('bidReceived') + expect(auctionAdUnit1.bids[0].bidder).to.equal('appnexus') + expect(auctionAdUnit1.bids[0].cpm).to.equal(0.112256) + expect(auctionAdUnit1.bids[0].cur).to.equal('USD') + expect(auctionAdUnit1.bids[0].s).to.equal(bidRequested.start) + expect(auctionAdUnit1.bids[0].e).to.equal(bidResponse.responseTimestamp) + expect(auctionAdUnit1.bids[0].rs).to.equal(bidRequested.start - auctionStart) + expect(auctionAdUnit1.bids[0].re).to.equal(bidResponse.responseTimestamp - auctionStart) + expect(auctionAdUnit1.bids[0].h).to.equal(0) + expect(auctionAdUnit1.bids[0].w).to.equal(0) + expect(auctionAdUnit1.bids[0].mt).to.equal('banner') + expect(auctionAdUnit1.bids[0].nrv).to.equal(true) + expect(auctionAdUnit1.bids[0].params).to.have.keys('placementId') + expect(auctionAdUnit1.bids[0].size).to.equal('0x0') + expect(auctionAdUnit1.bids[0].crId).to.equal('123456') + expect(auctionAdUnit1.bids[0].ttl).to.equal(bidResponse.ttl) + expect(auctionAdUnit1.bids[0].ttr).to.equal(bidResponse.timeToRespond) + + expect(auctionAdUnit2.auctionId).to.equal(auctionId) + expect(auctionAdUnit2.bids).to.be.an('array') + expect(auctionAdUnit2.bids).to.have.length(2) + + // no bid + expect(auctionAdUnit2.bids[0].bidId).to.equal(bidId2) + expect(auctionAdUnit2.bids[0].bst).to.equal('noBid') + expect(auctionAdUnit2.bids[0].bidder).to.equal('rubicon') + expect(auctionAdUnit2.bids[0].s).to.be.a('number') + expect(auctionAdUnit2.bids[0].e).to.be.a('number') + expect(auctionAdUnit2.bids[0].params).to.have.keys('placementId') + + // timeout + expect(auctionAdUnit2.bids[1].bidId).to.equal(bidId3) + expect(auctionAdUnit2.bids[1].bst).to.equal('bidTimedOut') + expect(auctionAdUnit2.bids[1].bidder).to.equal('ix') + expect(auctionAdUnit2.bids[1].s).to.be.a('number') + expect(auctionAdUnit2.bids[1].e).to.be.a('undefined') + }) + + it('should push events to a queue and process them once server configuration returns', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // configuration returned in an arbitrary moment + server.requests[0].respond(500) + + events.emit(constants.EVENTS.NO_BID, noBid); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody); + + expect(request).to.include({ + eventType: 'auction', + pid: 123, + auctionId, + auctionStart, + auctionEnd: 0, + timeout, + }) + expect(request.pvid).to.be.a('number') + expect(request.adUnits).to.have.length(2) + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.auctionId).to.equal(auctionId) + expect(auctionAdUnit1.bids).to.be.an('array') + expect(auctionAdUnit1.bids).to.have.length(1) + + // bid response + expect(auctionAdUnit1.bids[0].bidId).to.equal(bidId1) + expect(auctionAdUnit1.bids[0].bst).to.equal('bidReceived') + expect(auctionAdUnit1.bids[0].bidder).to.equal('appnexus') + expect(auctionAdUnit1.bids[0].cpm).to.equal(0.112256) + expect(auctionAdUnit1.bids[0].cur).to.equal('USD') + expect(auctionAdUnit1.bids[0].s).to.equal(bidRequested.start) + expect(auctionAdUnit1.bids[0].e).to.equal(bidResponse.responseTimestamp) + expect(auctionAdUnit1.bids[0].rs).to.equal(bidRequested.start - auctionStart) + expect(auctionAdUnit1.bids[0].re).to.equal(bidResponse.responseTimestamp - auctionStart) + expect(auctionAdUnit1.bids[0].h).to.equal(0) + expect(auctionAdUnit1.bids[0].w).to.equal(0) + expect(auctionAdUnit1.bids[0].mt).to.equal('banner') + expect(auctionAdUnit1.bids[0].nrv).to.equal(true) + expect(auctionAdUnit1.bids[0].params).to.have.keys('placementId') + expect(auctionAdUnit1.bids[0].size).to.equal('0x0') + expect(auctionAdUnit1.bids[0].crId).to.equal('123456') + expect(auctionAdUnit1.bids[0].ttl).to.equal(bidResponse.ttl) + expect(auctionAdUnit1.bids[0].ttr).to.equal(bidResponse.timeToRespond) + + expect(auctionAdUnit2.auctionId).to.equal(auctionId) + expect(auctionAdUnit2.bids).to.be.an('array') + expect(auctionAdUnit2.bids).to.have.length(2) + + // no bid + expect(auctionAdUnit2.bids[0].bidId).to.equal(bidId2) + expect(auctionAdUnit2.bids[0].bst).to.equal('noBid') + expect(auctionAdUnit2.bids[0].bidder).to.equal('rubicon') + expect(auctionAdUnit2.bids[0].s).to.be.a('number') + expect(auctionAdUnit2.bids[0].e).to.be.a('number') + expect(auctionAdUnit2.bids[0].params).to.have.keys('placementId') + + // timeout + expect(auctionAdUnit2.bids[1].bidId).to.equal(bidId3) + expect(auctionAdUnit2.bids[1].bst).to.equal('bidTimedOut') + expect(auctionAdUnit2.bids[1].bidder).to.equal('ix') + expect(auctionAdUnit2.bids[1].s).to.be.a('number') + expect(auctionAdUnit2.bids[1].e).to.be.a('undefined') + }) + + it('should send "auction" event without all the fields that were set to undefined', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + + simulateAuction() + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody); + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.code).to.equal(undefined) + expect(auctionAdUnit1.transactionId).to.equal(undefined) + expect(auctionAdUnit1.adUnitCode).to.equal(undefined) + expect(auctionAdUnit1.bids[0].auctionStart).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bids).to.equal(undefined) + expect(auctionAdUnit1.bids[0].refererInfo).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidRequestsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderRequestId).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderRequestsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderWinsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].schain).to.equal(undefined) + expect(auctionAdUnit1.bids[0].src).to.equal(undefined) + expect(auctionAdUnit1.bids[0].transactionId).to.equal(undefined) + + // no bid + expect(auctionAdUnit2.bids[0].schain).to.equal(undefined) + expect(auctionAdUnit2.bids[0].src).to.equal(undefined) + expect(auctionAdUnit2.bids[0].transactionId).to.equal(undefined) + }) + + it('should mark bid winner and send to the server along with the auction data', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + events.emit(constants.EVENTS.BID_WON, bidWon); + clock.tick(1500) + + // no bidWon + expect(server.requests).to.have.length(4) + + const request = JSON.parse(server.requests[3].requestBody); + expect(request.adUnits[0].bids[0].isW).to.equal(true) + }) + + it('should take BID_WON_TIMEOUT from server config if exists', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': {}, + 'BID_WON_TIMEOUT': 500 + })) + + simulateAuction() + events.emit(constants.EVENTS.BID_WON, bidWon); + clock.tick(499) + + // no auction data + expect(server.requests).to.have.length(3) + + clock.tick(1) + + // auction data + expect(server.requests).to.have.length(4) + const request = JSON.parse(server.requests[3].requestBody); + expect(request.adUnits[0].bids[0].isW).to.equal(true) + }) + + it('should send a "bidWon" event to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + clock.tick(1500) + events.emit(constants.EVENTS.BID_WON, bidWon); + + expect(server.requests).to.have.length(5) + + const request = JSON.parse(server.requests[4].requestBody); + + expect(request.eventType).to.equal('bidWon') + expect(request.auctionId).to.equal(auctionId) + expect(request.adunid).to.equal(adUnit1) + expect(request.bid.bst).to.equal('bidWon') + expect(request.bid.cur).to.equal('USD') + expect(request.bid.cpm).to.equal(0.5) + }) + + it('should sent adRenderFailed to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + clock.tick(1500) + events.emit(constants.EVENTS.AD_RENDER_FAILED, { bidId: 'abcdef', reason: 'exception' }); + + expect(server.requests).to.have.length(5) + + const request = JSON.parse(server.requests[4].requestBody); + + expect(request.eventType).to.equal('adRenderFailed') + expect(request.pvid).to.equal(PAGEVIEW_ID) + expect(request.bidId).to.equal('abcdef') + expect(request.reason).to.equal('exception') + }) + + it('should pick fields according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'bidRequested': { + 'sendRaw': 0, + 'pickFields': ['transactionId'] + }, + 'noBid': { + 'sendRaw': 0, + 'pickFields': ['src'] + }, + 'bidResponse': { + 'sendRaw': 0, + 'pickFields': ['adUrl', 'statusMessage'] + }, + 'auctionEnd': { + 'sendRaw': 0, + 'pickFields': ['winningBids'] + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); + events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.transactionId = '123'; return b }) }); + events.emit(constants.EVENTS.NO_BID, { ...noBid, src: 'client' }); + events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, adUrl: '...' }); + events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd, winningBids: [] }); + events.emit(constants.EVENTS.BID_WON, { ...bidWon, statusMessage: 'msg2' }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.adUnits[0].bids[0].transactionId).to.equal('123') + expect(request.adUnits[0].bids[0].adUrl).to.equal('...') + expect(request.adUnits[0].bids[0].statusMessage).to.equal('msg2') + expect(request.adUnits[1].bids[0].src).to.equal('client') + }) + + it('should omit fields according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 0, + 'omitFields': ['custom_1'] + }, + 'bidResponse': { + 'sendRaw': 0, + 'omitFields': ['custom_2', 'custom_4', 'custom_5'] + } + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.custom_2 = true; return b }) }); + events.emit(constants.EVENTS.NO_BID, { ...noBid, custom_3: true }); + events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, custom_4: true }); + events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd }); + events.emit(constants.EVENTS.BID_WON, { ...bidWon, custom_5: true }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.custom_1).to.equal(undefined) + expect(request.custom_6).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_2).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_4).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_5).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_7).to.equal(undefined) + }) + + it('should omit fields from raw data according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + 'omitRawFields': ['custom_1'] + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.eventType).to.equal('auctionInit') + expect(request.custom_1).to.equal(undefined) + }) + + it('should send raw data to custom endpoint if exists in server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + 'endpoint': 'https://pbjs.com' + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); + + expect(server.requests[3].url).to.equal('https://pbjs.com') + }) + + it('should send raw events based on server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 0, + }, + 'bidRequested': { + 'sendRaw': 1, + } + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit) + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request).to.deep.equal({ + eventType: 'bidRequested', + pid: 123, + pvid: PAGEVIEW_ID, + pbModuleVersion: '1.0.0', + ...bidRequested + }) + }) + + it('should queue events and raw events until server configuration resolves', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + simulateAuction() + clock.tick(1500) + + expect(server.requests).to.have.length(3) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + }, + 'bidRequested': { + 'sendRaw': 1, + } + } + })) + + expect(server.requests).to.have.length(5) + expect(JSON.parse(server.requests[3].requestBody).eventType).to.equal('auctionInit') + expect(JSON.parse(server.requests[4].requestBody).eventType).to.equal('bidRequested') + }) + }); + + describe('buildAuctionData', () => { + let auction = { + auctionId, + auctionStart, + auctionEnd, + adUnitCodes: ['mid_1'], + auctionStatus: 'running', + bidderRequests: [], + bidsReceived: [], + noBids: [], + winningBids: [], + timestamp: 1234567, + config: {}, + adUnits: { + mid_1: { + adUnitCode: 'mid_1', + code: 'mid_1', + transactionId: '123dsafasdf', + bids: { + [bidId1]: { + adUnitCode: 'mid_1', + cpm: 0.5 + } + } + } + } + } + + it('should turn adUnits and bids objects into arrays', () => { + const auctionData = buildAuctionData(auction, []) + + expect(auctionData.adUnits).to.be.an('array') + expect(auctionData.adUnits[0].bids).to.be.an('array') + }) + + it('should remove fields from the auction', () => { + const auctionData = buildAuctionData(auction, []) + const auctionFields = Object.keys(auctionData) + const adUnitFields = Object.keys(auctionData.adUnits[0]) + + expect(auctionFields).not.to.contain('adUnitCodes') + expect(auctionFields).not.to.contain('auctionStatus') + expect(auctionFields).not.to.contain('bidderRequests') + expect(auctionFields).not.to.contain('bidsReceived') + expect(auctionFields).not.to.contain('noBids') + expect(auctionFields).not.to.contain('winningBids') + expect(auctionFields).not.to.contain('timestamp') + expect(auctionFields).not.to.contain('config') + + expect(adUnitFields).not.to.contain('adUnitCoe') + expect(adUnitFields).not.to.contain('code') + expect(adUnitFields).not.to.contain('transactionId') + }) + }) + + describe('generatePageViewId', () => { + it('should generate a 19 digits number', () => { + expect(generatePageViewId()).length(19) + }) + }) +}); 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 805435abf80..d7d2d31669c 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -1,114 +1,193 @@ import { expect } from 'chai'; -import openxAdapter from 'modules/openxAnalyticsAdapter.js'; -import { config } from 'src/config.js'; +import openxAdapter, {AUCTION_STATES} from 'modules/openxAnalyticsAdapter.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import find from 'core-js-pure/features/array/find.js'; const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON, AUCTION_END } } = CONSTANTS; - const SLOT_LOADED = 'slotOnload'; +const CURRENT_TIME = 1586000000000; describe('openx analytics adapter', function() { - it('should require publisher id', function() { - sinon.spy(utils, 'logError'); + describe('when validating the configuration', function () { + let spy; + beforeEach(function () { + spy = sinon.spy(utils, 'logError'); + }); + + afterEach(function() { + utils.logError.restore(); + }); + + it('should require organization id when no configuration is passed', function() { + openxAdapter.enableAnalytics(); + expect(spy.firstCall.args[0]).to.match(/publisherPlatformId/); + expect(spy.firstCall.args[0]).to.match(/to exist/); + }); - openxAdapter.enableAnalytics(); - expect( - utils.logError.calledWith( - 'OpenX analytics adapter: publisherId is required.' - ) - ).to.be.true; + it('should require publisher id when no orgId is passed', function() { + openxAdapter.enableAnalytics({ + provider: 'openx', + options: { + publisherAccountId: 12345 + } + }); + expect(spy.firstCall.args[0]).to.match(/publisherPlatformId/); + expect(spy.firstCall.args[0]).to.match(/to exist/); + }); - utils.logError.restore(); + it('should validate types', function() { + openxAdapter.enableAnalytics({ + provider: 'openx', + options: { + orgId: 'test platformId', + sampling: 'invalid-float' + } + }); + + expect(spy.firstCall.args[0]).to.match(/sampling/); + expect(spy.firstCall.args[0]).to.match(/type 'number'/); + }); }); - describe('sending analytics event', function() { - const auctionInit = { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' }; + describe('when tracking analytic events', function () { + const AD_UNIT_CODE = 'test-div-1'; + const SLOT_LOAD_WAIT_TIME = 10; + + const DEFAULT_V2_ANALYTICS_CONFIG = { + orgId: 'test-org-id', + publisherAccountId: 123, + publisherPlatformId: 'test-platform-id', + configId: 'my_config', + optimizerConfig: 'my my optimizer', + sample: 1.0, + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME + }; + + const auctionInit = { + auctionId: 'test-auction-id', + timestamp: CURRENT_TIME, + timeout: 3000, + adUnitCodes: [AD_UNIT_CODE], + }; const bidRequestedOpenX = { - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', - auctionStart: 1540944528017, + auctionId: 'test-auction-id', + auctionStart: CURRENT_TIME, + timeout: 2000, bids: [ { - adUnitCode: 'div-1', - bidId: '2f0c647b904e25', + adUnitCode: AD_UNIT_CODE, + bidId: 'test-openx-request-id', bidder: 'openx', - params: { unit: '540249866' }, - transactionId: 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873' + params: { unit: 'test-openx-ad-unit-id' }, + userId: { + tdid: 'test-tradedesk-id', + empty_id: '', + null_id: null, + bla_id: '', + digitrustid: { data: { id: '1' } }, + lipbid: { lipb: '2' } + } } ], - start: 1540944528021 + start: CURRENT_TIME + 10 }; const bidRequestedCloseX = { - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', - auctionStart: 1540944528017, + auctionId: 'test-auction-id', + auctionStart: CURRENT_TIME, + timeout: 1000, bids: [ { - adUnitCode: 'div-1', - bidId: '43d454020e9409', + adUnitCode: AD_UNIT_CODE, + bidId: 'test-closex-request-id', bidder: 'closex', - params: { unit: '513144370' }, - transactionId: 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873' + params: { unit: 'test-closex-ad-unit-id' }, + userId: { + bla_id: '2', + tdid: 'test-tradedesk-id' + } } ], - start: 1540944528026 + start: CURRENT_TIME + 20 }; const bidResponseOpenX = { - requestId: '2f0c647b904e25', - adId: '33dddbb61d359a', - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + adUnitCode: AD_UNIT_CODE, cpm: 0.5, + netRevenue: true, + requestId: 'test-openx-request-id', + mediaType: 'banner', + width: 300, + height: 250, + adId: 'test-openx-ad-id', + auctionId: 'test-auction-id', creativeId: 'openx-crid', - responseTimestamp: 1540944528184, - ts: '2DAABBgABAAECAAIBAAsAAgAAAJccGApKSGt6NUZxRXYyHBbinsLj' + currency: 'USD', + timeToRespond: 100, + responseTimestamp: CURRENT_TIME + 30, + ts: 'test-openx-ts' }; const bidResponseCloseX = { - requestId: '43d454020e9409', - adId: '43dddbb61d359a', - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + adUnitCode: AD_UNIT_CODE, cpm: 0.3, + netRevenue: true, + requestId: 'test-closex-request-id', + mediaType: 'video', + width: 300, + height: 250, + adId: 'test-closex-ad-id', + auctionId: 'test-auction-id', creativeId: 'closex-crid', - responseTimestamp: 1540944528196, - ts: 'hu1QWo6iD3MHs6NG_AQAcFtyNqsj9y4S0YRbX7Kb06IrGns0BABb' + currency: 'USD', + timeToRespond: 200, + dealId: 'test-closex-deal-id', + responseTimestamp: CURRENT_TIME + 40, + ts: 'test-closex-ts' }; const bidTimeoutOpenX = { 0: { - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', - bidId: '2f0c647b904e25' - } - }; + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-openx-request-id' + }}; const bidTimeoutCloseX = { 0: { - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', - bidId: '43d454020e9409' + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-closex-request-id' } }; const bidWonOpenX = { - requestId: '2f0c647b904e25', - adId: '33dddbb61d359a', - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' + requestId: 'test-openx-request-id', + adId: 'test-openx-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' + }; + + const auctionEnd = { + auctionId: 'test-auction-id', + timestamp: CURRENT_TIME, + auctionEnd: CURRENT_TIME + 100, + timeout: 3000, + adUnitCodes: [AD_UNIT_CODE], }; const bidWonCloseX = { - requestId: '43d454020e9409', - adId: '43dddbb61d359a', - adUnitCode: 'div-1', - auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' + requestId: 'test-closex-request-id', + adId: 'test-closex-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' }; function simulateAuction(events) { @@ -116,328 +195,458 @@ describe('openx analytics adapter', function() { events.forEach(event => { const [eventType, args] = event; - openxAdapter.track({ eventType, args }); if (eventType === BID_RESPONSE) { highestBid = highestBid || args; if (highestBid.cpm < args.cpm) { highestBid = args; } } - }); - openxAdapter.track({ - eventType: SLOT_LOADED, - args: { - slot: { - getAdUnitPath: () => { - return '/90577858/test_ad_unit'; - }, - getTargetingKeys: () => { - return []; - }, - getTargeting: sinon - .stub() - .withArgs('hb_adid') - .returns(highestBid ? [highestBid.adId] : []) - } + if (eventType === SLOT_LOADED) { + const slotLoaded = { + slot: { + getAdUnitPath: () => { + return '/12345678/test_ad_unit'; + }, + getSlotElementId: () => { + return AD_UNIT_CODE; + }, + getTargeting: (key) => { + if (key === 'hb_adid') { + return highestBid ? [highestBid.adId] : []; + } else { + return []; + } + } + } + }; + openxAdapter.track({ eventType, args: slotLoaded }); + } else { + openxAdapter.track({ eventType, args }); } }); } - function getQueryData(url) { - const queryArgs = url.split('?')[1].split('&'); - return queryArgs.reduce((data, arg) => { - const [key, val] = arg.split('='); - data[key] = val; - return data; - }, {}); - } + let clock; - before(function() { + beforeEach(function() { sinon.stub(events, 'getEvents').returns([]); - openxAdapter.enableAnalytics({ - options: { - publisherId: 'test123' - } - }); + clock = sinon.useFakeTimers(CURRENT_TIME); }); - after(function() { + afterEach(function() { events.getEvents.restore(); - openxAdapter.disableAnalytics(); + clock.restore(); }); - beforeEach(function() { - openxAdapter.reset(); - }); + describe('when there is an auction', function () { + let auction; + let auction2; + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); - afterEach(function() {}); + simulateAuction([ + [AUCTION_INIT, auctionInit], + [SLOT_LOADED] + ]); - it('should not send request if no bid response', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX] - ]); + simulateAuction([ + [AUCTION_INIT, {...auctionInit, auctionId: 'second-auction-id'}], + [SLOT_LOADED] + ]); - expect(server.requests.length).to.equal(0); - }); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + auction2 = JSON.parse(server.requests[1].requestBody)[0]; + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - it('should send 1 request to the right endpoint', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] - ]); + it('should track auction start time', function () { + expect(auction.startTime).to.equal(auctionInit.timestamp); + }); + + it('should track auction time limit', function () { + expect(auction.timeLimit).to.equal(auctionInit.timeout); + }); - expect(server.requests.length).to.equal(1); + it('should track the \'default\' test code', function () { + expect(auction.testCode).to.equal('default'); + }); + + it('should track auction count', function () { + expect(auction.auctionOrder).to.equal(1); + expect(auction2.auctionOrder).to.equal(2); + }); + + it('should track the orgId', function () { + expect(auction.orgId).to.equal(DEFAULT_V2_ANALYTICS_CONFIG.orgId); + }); - const endpoint = server.requests[0].url.split('?')[0]; - // note IE11 returns the default secure port, so we look for this alternate value as well in these tests - expect(endpoint).to.be.oneOf(['https://ads.openx.net/w/1.0/pban', 'https://ads.openx.net:443/w/1.0/pban']); + it('should track the orgId', function () { + expect(auction.publisherPlatformId).to.equal(DEFAULT_V2_ANALYTICS_CONFIG.publisherPlatformId); + }); + + 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('hb.ct, hb.rid, dddid, hb.asiid, hb.pubid', function() { - it('should always be in the query string', function() { + describe('when there is a custom test code', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + ...DEFAULT_V2_ANALYTICS_CONFIG, + testCode: 'test-code' + } + }); + simulateAuction([ [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [SLOT_LOADED], ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - 'hb.ct': String(bidRequestedOpenX.auctionStart), - 'hb.rid': auctionInit.auctionId, - dddid: bidRequestedOpenX.bids[0].transactionId, - 'hb.asiid': '/90577858/test_ad_unit', - 'hb.pubid': 'test123' - }); + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track the custom test code', function () { + expect(auction.testCode).to.equal('test-code'); }); }); - describe('hb.cur', function() { - it('should be in the query string if currency is set', function() { - sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') - .returns('bitcoin'); + describe('when there is campaign (utm) data', function () { + let auction; + beforeEach(function () { + + }); + + afterEach(function () { + openxAdapter.reset(); + utils.getWindowLocation.restore(); + openxAdapter.disableAnalytics(); + }); + + it('should track values from query params when they exist', function () { + sinon.stub(utils, 'getWindowLocation').returns({search: '?' + + 'utm_campaign=test%20campaign-name&' + + 'utm_source=test-source&' + + 'utm_medium=test-medium&' + }); + + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [SLOT_LOADED], ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + // ensure that value are URI decoded + expect(auction.campaign.name).to.equal('test campaign-name'); + expect(auction.campaign.source).to.equal('test-source'); + expect(auction.campaign.medium).to.equal('test-medium'); + expect(auction.campaign.content).to.be.undefined; + expect(auction.campaign.term).to.be.undefined; + }); - config.getConfig.restore(); + it('should override query params if configuration parameters exist', function () { + sinon.stub(utils, 'getWindowLocation').returns({search: '?' + + 'utm_campaign=test-campaign-name&' + + 'utm_source=test-source&' + + 'utm_medium=test-medium&' + + 'utm_content=test-content&' + + 'utm_term=test-term' + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - 'hb.cur': 'bitcoin' + openxAdapter.enableAnalytics({ + options: { + ...DEFAULT_V2_ANALYTICS_CONFIG, + campaign: { + name: 'test-config-name', + source: 'test-config-source', + medium: 'test-config-medium' + } + } }); - }); - it('should not be in the query string if currency is not set', function() { simulateAuction([ [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [SLOT_LOADED], ]); - - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.not.have.key('hb.cur'); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + expect(auction.campaign.name).to.equal('test-config-name'); + expect(auction.campaign.source).to.equal('test-config-source'); + expect(auction.campaign.medium).to.equal('test-config-medium'); + expect(auction.campaign.content).to.equal('test-content'); + expect(auction.campaign.term).to.equal('test-term'); }); }); - describe('hb.dcl, hb.dl, hb.tta, hb.ttr', function() { - it('should be in the query string if browser supports performance API', function() { - const timing = { - fetchStart: 1540944528000, - domContentLoadedEventEnd: 1540944528010, - loadEventEnd: 1540944528110 - }; - const originalPerf = window.top.performance; - window.top.performance = { timing }; + describe('when there are bid requests', function () { + let auction; + let openxBidder; + let closexBidder; - const renderTime = 1540944528100; - sinon.stub(Date, 'now').returns(renderTime); + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [SLOT_LOADED], ]); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; + openxBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx'); + closexBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'closex'); + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - window.top.performance = originalPerf; - Date.now.restore(); + it('should track the bidder', function () { + expect(openxBidder.bidder).to.equal('openx'); + expect(closexBidder.bidder).to.equal('closex'); + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - 'hb.dcl': String(timing.domContentLoadedEventEnd - timing.fetchStart), - 'hb.dl': String(timing.loadEventEnd - timing.fetchStart), - 'hb.tta': String(bidRequestedOpenX.auctionStart - timing.fetchStart), - 'hb.ttr': String(renderTime - timing.fetchStart) - }); + it('should track the adunit code', function () { + expect(auction.adUnits[0].code).to.equal(AD_UNIT_CODE); }); - it('should not be in the query string if browser does not support performance API', function() { - const originalPerf = window.top.performance; - window.top.performance = undefined; + it('should track the user ids', function () { + expect(auction.userIdProviders).to.deep.equal(['bla_id', 'digitrustid', 'lipbid', 'tdid']); + }); + + it('should not have responded', function () { + expect(openxBidder.hasBidderResponded).to.equal(false); + expect(closexBidder.hasBidderResponded).to.equal(false); + }); + }); + + describe('when there are request timeouts', function () { + let auction; + let openxBidRequest; + let closexBidRequest; + + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [BID_TIMEOUT, bidTimeoutCloseX], + [BID_TIMEOUT, bidTimeoutOpenX], + [AUCTION_END, auctionEnd] ]); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + openxBidRequest = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx'); + closexBidRequest = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'closex'); + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - window.top.performance = originalPerf; + it('should track the timeout', function () { + expect(openxBidRequest.timedOut).to.equal(true); + expect(closexBidRequest.timedOut).to.equal(true); + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.not.have.keys( - 'hb.dcl', - 'hb.dl', - 'hb.tta', - 'hb.ttr' - ); + it('should track the timeout value ie timeLimit', function () { + expect(openxBidRequest.timeLimit).to.equal(2000); + expect(closexBidRequest.timeLimit).to.equal(1000); }); }); - describe('ts, auid', function() { - it('OpenX is in auction and has a bid response', function() { + describe('when there are bid responses', function () { + let auction; + let openxBidResponse; + let closexBidResponse; + + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); + simulateAuction([ [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], [BID_REQUESTED, bidRequestedCloseX], + [BID_REQUESTED, bidRequestedOpenX], [BID_RESPONSE, bidResponseOpenX], - [BID_RESPONSE, bidResponseCloseX] + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd] ]); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - ts: bidResponseOpenX.ts, - auid: bidRequestedOpenX.bids[0].params.unit - }); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + openxBidResponse = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx').bidResponses[0]; + closexBidResponse = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'closex').bidResponses[0]; }); - it('OpenX is in auction but no bid response', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_REQUESTED, bidRequestedCloseX], - [BID_RESPONSE, bidResponseCloseX] - ]); + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - auid: bidRequestedOpenX.bids[0].params.unit - }); - expect(queryData).to.not.have.key('ts'); + it('should track the cpm in microCPM', function () { + expect(openxBidResponse.microCpm).to.equal(bidResponseOpenX.cpm * 1000000); + expect(closexBidResponse.microCpm).to.equal(bidResponseCloseX.cpm * 1000000); }); - it('OpenX is not in auction', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedCloseX], - [BID_RESPONSE, bidResponseCloseX] - ]); + it('should track if the bid is in net revenue', function () { + expect(openxBidResponse.netRevenue).to.equal(bidResponseOpenX.netRevenue); + expect(closexBidResponse.netRevenue).to.equal(bidResponseCloseX.netRevenue); + }); + + it('should track the mediaType', function () { + expect(openxBidResponse.mediaType).to.equal(bidResponseOpenX.mediaType); + expect(closexBidResponse.mediaType).to.equal(bidResponseCloseX.mediaType); + }); + + it('should track the currency', function () { + expect(openxBidResponse.currency).to.equal(bidResponseOpenX.currency); + expect(closexBidResponse.currency).to.equal(bidResponseCloseX.currency); + }); + + it('should track the ad width and height', function () { + expect(openxBidResponse.width).to.equal(bidResponseOpenX.width); + expect(openxBidResponse.height).to.equal(bidResponseOpenX.height); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.not.have.keys('auid', 'ts'); + expect(closexBidResponse.width).to.equal(bidResponseCloseX.width); + expect(closexBidResponse.height).to.equal(bidResponseCloseX.height); + }); + + it('should track the bid dealId', function () { + expect(openxBidResponse.dealId).to.equal(bidResponseOpenX.dealId); // no deal id defined + expect(closexBidResponse.dealId).to.equal(bidResponseCloseX.dealId); // deal id defined + }); + + it('should track the bid\'s latency', function () { + expect(openxBidResponse.latency).to.equal(bidResponseOpenX.timeToRespond); + expect(closexBidResponse.latency).to.equal(bidResponseCloseX.timeToRespond); + }); + + it('should not have any bid winners', function () { + expect(openxBidResponse.winner).to.equal(false); + expect(closexBidResponse.winner).to.equal(false); + }); + + it('should track the bid currency', function () { + expect(openxBidResponse.currency).to.equal(bidResponseOpenX.currency); + expect(closexBidResponse.currency).to.equal(bidResponseCloseX.currency); + }); + + it('should track the auction end time', function () { + expect(auction.endTime).to.equal(auctionEnd.auctionEnd); + }); + + it('should track that the auction ended', function () { + expect(auction.state).to.equal(AUCTION_STATES.ENDED); }); }); - describe('hb.exn, hb.sts, hb.ets, hb.bv, hb.crid, hb.to', function() { - it('2 bidders in auction', function() { + describe('when there are bidder wins', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); + simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], [BID_REQUESTED, bidRequestedCloseX], [BID_RESPONSE, bidResponseOpenX], - [BID_RESPONSE, bidResponseCloseX] + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd], + [BID_WON, bidWonOpenX] ]); - const queryData = getQueryData(server.requests[0].url); - const auctionStart = bidRequestedOpenX.auctionStart; - expect(queryData).to.include({ - 'hb.exn': [ - bidRequestedOpenX.bids[0].bidder, - bidRequestedCloseX.bids[0].bidder - ].join(','), - 'hb.sts': [ - bidRequestedOpenX.start - auctionStart, - bidRequestedCloseX.start - auctionStart - ].join(','), - 'hb.ets': [ - bidResponseOpenX.responseTimestamp - auctionStart, - bidResponseCloseX.responseTimestamp - auctionStart - ].join(','), - 'hb.bv': [bidResponseOpenX.cpm, bidResponseCloseX.cpm].join(','), - 'hb.crid': [ - bidResponseOpenX.creativeId, - bidResponseCloseX.creativeId - ].join(','), - 'hb.to': [false, false].join(',') - }); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; }); - it('OpenX timed out', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_REQUESTED, bidRequestedCloseX], - [BID_RESPONSE, bidResponseCloseX], - [BID_TIMEOUT, bidTimeoutOpenX] - ]); + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - const queryData = getQueryData(server.requests[0].url); - const auctionStart = bidRequestedOpenX.auctionStart; - expect(queryData).to.include({ - 'hb.exn': [ - bidRequestedOpenX.bids[0].bidder, - bidRequestedCloseX.bids[0].bidder - ].join(','), - 'hb.sts': [ - bidRequestedOpenX.start - auctionStart, - bidRequestedCloseX.start - auctionStart - ].join(','), - 'hb.ets': [ - undefined, - bidResponseCloseX.responseTimestamp - auctionStart - ].join(','), - 'hb.bv': [0, bidResponseCloseX.cpm].join(','), - 'hb.crid': [undefined, bidResponseCloseX.creativeId].join(','), - 'hb.to': [true, false].join(',') - }); + it('should track that bidder as the winner', function () { + let openxBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({winner: true}); + }); + + it('should track that bidder as the losers', function () { + let closexBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'closex'); + expect(closexBidder.bidResponses[0]).to.contain({winner: false}); }); }); - describe('hb.we, hb.g1', function() { - it('OpenX won', function() { + describe('when a winning bid renders', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); + simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], [BID_RESPONSE, bidResponseOpenX], - [BID_WON, bidWonOpenX] + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd], + [BID_WON, bidWonOpenX], + [SLOT_LOADED] ]); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - 'hb.we': '0', - 'hb.g1': 'false' - }); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; }); - it('DFP won', function() { - simulateAuction([ - [AUCTION_INIT, auctionInit], - [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] - ]); + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); - const queryData = getQueryData(server.requests[0].url); - expect(queryData).to.include({ - 'hb.we': '-1', - 'hb.g1': 'true' - }); + it('should track that winning bid rendered', function () { + let openxBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({rendered: true}); + }); + + it('should track that winning bid render time', function () { + let openxBidder = find(auction.adUnits[0].bidRequests, bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({renderTime: CURRENT_TIME}); + }); + + it('should track that the auction completed', function () { + expect(auction.state).to.equal(AUCTION_STATES.COMPLETED); }); }); }); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index a6fbc9666b9..96e545d70fa 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -121,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'); @@ -180,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); }); }); }); @@ -290,41 +349,43 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); + + describe('and request config uses test', () => { + const videoBidWithTest = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + test: true + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + let mockBidderRequest = {refererInfo: {}}; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); + }); + + it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { + const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); + expect(request[0].data.vtest).to.equal(1); + }); + }); }); }); 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', @@ -417,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 () { @@ -508,25 +574,6 @@ describe('OpenxAdapter', function () { expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); }); - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - it('should send out custom bc parameter, if override is present', function () { const bidRequest = Object.assign({}, bidRequestsWithMediaTypes[0], @@ -1029,14 +1076,32 @@ describe('OpenxAdapter', function () { const EXAMPLE_DATA_BY_ATTR = { britepoolid: '1111-britepoolid', criteoId: '1111-criteoId', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - id5id: '1111-id5id', + fabrickId: '1111-fabrickid', + haloId: '1111-haloid', + id5id: {uid: '1111-id5id'}, idl_env: '1111-idl_env', + IDP: '1111-zeotap-idplusid', + idxId: '1111-idxid', + intentIqId: '1111-intentiqid', lipb: {lipbid: '1111-lipb'}, + lotamePanoramaId: '1111-lotameid', + merkleId: '1111-merkleid', netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', + parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, pubcid: '1111-pubcid', + quantcastId: '1111-quantcastid', + tapadId: '111-tapadid', tdid: '1111-tdid', + 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', }; // generates the same set of tests for each id provider @@ -1074,12 +1139,24 @@ describe('OpenxAdapter', function () { let userIdValue; // handle cases where userId key refers to an object switch (userIdProviderKey) { - case 'digitrustid': - userIdValue = EXAMPLE_DATA_BY_ATTR.digitrustid.data.id; + 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; + case 'parrableId': + userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; + break; + 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]; } @@ -1089,27 +1166,113 @@ describe('OpenxAdapter', function () { }); }); }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 9.99 + } + } + } + ); + + const bidRequest2 = Object.assign({}, + bidRequestsWithMediaTypes[1], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 18.881 + } + } + } + ); + + const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('9990,18881'); + }); + + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; + } + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + }); + + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; + } + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + }); + }) + }) }); 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 () { @@ -1124,6 +1287,12 @@ 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 () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.vtest).to.be.undefined; }); it('should send a bc parameter', function () { @@ -1178,81 +1347,329 @@ 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); }); }); }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + function makeBidWithFloorInfo(floorInfo) { + return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), + { + getFloor: () => { + return floorInfo; + } + }); + } + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const floors = [9.99, 18.881]; + const bidRequests = floors.map(floor => { + return makeBidWithFloorInfo({ + currency: 'AUS', + floor: floor + }); + }); + const request = spec.buildRequests(bidRequests, mockBidderRequest); + + expect(request[0].data.aumfs).to.exist; + expect(request[0].data.aumfs).to.equal('9990'); + expect(request[1].data.aumfs).to.exist; + expect(request[1].data.aumfs).to.equal('18881'); + }); + + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + }); + + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + }); + }) + }) }); 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 first party config', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp2', segment: [{id: 'baz'}]}, + ] + } + } + }, + expect: 'dmp1/4:foo|bar,dmp2:baz', + }, + { + name: 'should combine same provider segment data from first party config', + config: { + ortb2: { + user: { + data: [ + {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, + {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, + ] + } + } + }, + expect: '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'}]}, + ] + } + } + }, + request: { + userId: { + lipb: { + lipbid: 'aaa', + segments: ['l1', 'l2'] + }, + }, + }, + expect: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', + }, + { + name: 'should send just liveintent segment from request if no first party config', + config: {}, + request: { + userId: { + lipb: { + lipbid: 'aaa', + segments: ['l1', 'l2'] + }, + }, + }, + expect: '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; + let configStub; + + beforeEach(function () { + let fpdConfig = t.config + configStub = sinon + .stub(config, 'getConfig') + .withArgs('ortb2.user.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) { + expect(request[0].data.sm).to.exist; + expect(request[0].data.sm).to.equal(encodeURIComponent(t.expect)); + } else { + expect(request[0].data.sm).to.not.exist; + } + }); + }); + }); + }); + }); + }) + describe('interpretResponse for banner ads', function () { beforeEach(function () { sinon.spy(userSync, 'registerSync'); @@ -1354,7 +1771,11 @@ describe('OpenxAdapter', function () { expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); }); - it('should return a brand ID', function () { + it('should return an adomain', function () { + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + + it('should return a dsp ID', function () { expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); }); }); @@ -1634,6 +2055,13 @@ describe('OpenxAdapter', function () { expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); }); + it('should return correct bid response with MediaType and deal_id', function () { + const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; + const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); + const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); + expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); + }); + it('should handle nobid responses for bidRequests with MediaTypes', function () { const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); diff --git a/test/spec/modules/operaadsBidAdapter_spec.js b/test/spec/modules/operaadsBidAdapter_spec.js new file mode 100644 index 00000000000..c225835fcc2 --- /dev/null +++ b/test/spec/modules/operaadsBidAdapter_spec.js @@ -0,0 +1,705 @@ +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.id).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.id).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.id).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', function () { + it('getUserSyncs should return empty array', function () { + expect(spec.getUserSyncs()).to.be.an('array').that.is.empty; + }); + }); + + 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..8b1866d044a --- /dev/null +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -0,0 +1,89 @@ +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'], + }, + 'mo': { + 'div-0': ['A9', 'B0'], + 'div-1': ['B1', 'B2'], + } + } + }; + it('Properly set the score file url', () => { + 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']); + }); +}); + +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/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b5b76ce3fde --- /dev/null +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -0,0 +1,40 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' + +const AD_UNIT_CODE = 'demo-adunit-1'; +const PUBLISHER_CONFIG = { + pubId: 'optimon_test', + pubAdxAccount: 123456789, + pubTimezone: 'Asia/Jerusalem' +}; + +describe('Optimon Analytics Adapter', () => { + const optmn_currentWindow = utils.getWindowSelf(); + let optmn_queue = []; + + beforeEach(() => { + optmn_currentWindow.OptimonAnalyticsAdapter = (...optmn_args) => optmn_queue.push(optmn_args); + adapterManager.enableAnalytics({ + provider: 'optimon' + }); + optmn_queue = [] + }); + + afterEach(() => { + optimonAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward all events to the queue', () => { + const optmn_arguments = [AD_UNIT_CODE, PUBLISHER_CONFIG]; + + events.emit(constants.EVENTS.AUCTION_END, optmn_arguments) + events.emit(constants.EVENTS.BID_TIMEOUT, optmn_arguments) + events.emit(constants.EVENTS.BID_WON, optmn_arguments) + + expect(optmn_queue.length).to.eql(3); + }); +}); 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 eec6ccd19f6..0a18799ad4b 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -1,12 +1,12 @@ import {expect} from 'chai'; import {spec} from 'modules/orbidderBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import openxAdapter from '../../../modules/openxAnalyticsAdapter.js'; -import {detectReferer} from 'src/refererDetection.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', @@ -16,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 + } + } } }; @@ -43,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('accepts optional profile object', () => { - const bidRequest = deepClone(defaultBidRequest); + 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('performs type checking', () => { - 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('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('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('doesn\'t accept malformed profile', () => { - const bidRequest = deepClone(defaultBidRequest); + 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('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('accepts optional bidfloor', () => { - const bidRequest = deepClone(defaultBidRequest); + it('banner: accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequestBanner); bidRequest.params.bidfloor = 123; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); @@ -80,53 +140,95 @@ 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'); expect(request.url.indexOf('https://')).to.equal(0); - expect(request.url).to.equal(`${spec.orbidderHost}/bid`); + expect(request.url).to.equal(`${spec.hostname}/bid`); }); it('contains prebid version parameter', () => { 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('native: handles non-existent gdpr object', () => { + const request = buildRequest(defaultBidRequestNative, { gdprConsent: null }); expect(request.data.gdprConsent).to.be.undefined; }); - it('handles properly filled gdpr object where gdpr applies', () => { + it('banner: handles properly filled gdpr object where gdpr applies', () => { const consentString = 'someWeirdString'; - const request = buildRequest(defaultBidRequest, { + const request = buildRequest(defaultBidRequestBanner, { gdprConsent: { gdprApplies: true, consentString: consentString @@ -138,9 +240,37 @@ describe('orbidderBidAdapter', () => { expect(gdprConsent.consentString).to.be.equal(consentString); }); - it('handles properly filled gdpr object where gdpr does not apply', () => { + it('native: handles properly filled gdpr object where gdpr applies', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequestNative, { + 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('banner: handles properly filled gdpr object where gdpr does not apply', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequestBanner, { + gdprConsent: { + gdprApplies: false, + consentString: consentString + } + }); + + const gdprConsent = request.data.gdprConsent; + 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(defaultBidRequest, { + const request = buildRequest(defaultBidRequestNative, { gdprConsent: { gdprApplies: false, consentString: consentString @@ -154,7 +284,43 @@ describe('orbidderBidAdapter', () => { }); describe('interpretResponse', () => { - it('should get correct bid response', () => { + it('banner: should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR', + 'mediaType': BANNER, + } + ]; + + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 60, + 'currency': 'EUR', + 'ad': '', + '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, @@ -165,7 +331,9 @@ describe('orbidderBidAdapter', () => { 'requestId': '30b31c1838de1e', 'ttl': 60, 'netRevenue': true, - 'currency': 'EUR' + 'currency': 'EUR', + 'advertiserDomains': ['cm.tavira.pt'], + 'mediaType': BANNER } ]; @@ -179,7 +347,11 @@ describe('orbidderBidAdapter', () => { 'ttl': 60, 'currency': 'EUR', 'ad': '', - 'netRevenue': true + 'netRevenue': true, + 'meta': { + 'advertiserDomains': ['cm.tavira.pt'] + }, + 'mediaType': BANNER } ]; @@ -187,28 +359,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 deleted file mode 100644 index 8ac01c1657e..00000000000 --- a/test/spec/modules/otmBidAdapter_spec.js +++ /dev/null @@ -1,105 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/otmBidAdapter.js'; - -describe('otmBidAdapterTests', function () { - it('validate_pub_params', function () { - expect(spec.isBidRequestValid({ - bidder: 'otm', - params: { - tid: '123', - bidfloor: 20 - } - })).to.equal(true); - }); - - it('validate_generated_params', function () { - let bidRequestData = [{ - bidId: 'bid1234', - bidder: 'otm', - params: { - tid: '123', - bidfloor: 20 - }, - sizes: [[240, 400]] - }]; - - let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; - - 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: { - bidId: 'bid1234' - } - }; - - let serverResponse = { - body: [ - { - 'auctionid': '3c6f8e22-541b-485c-9214-e974d9fb1b6f', - 'cpm': 847.097, - 'ad': 'test html', - 'w': 240, - 'h': 400, - 'currency': 'RUB', - 'ttl': 300, - 'creativeid': '1_7869053', - 'bidid': '101f211def7c99', - 'transactionid': 'transaction_id_1' - } - ] - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData); - expect(bids).to.have.lengthOf(1); - let bid = bids[0]; - expect(bid.cpm).to.equal(847.097); - expect(bid.currency).to.equal('RUB'); - expect(bid.width).to.equal(240); - expect(bid.height).to.equal(400); - expect(bid.netRevenue).to.equal(true); - expect(bid.requestId).to.equal('101f211def7c99'); - expect(bid.ad).to.equal('test html'); - }); -}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js new file mode 100644 index 00000000000..a5f23240a7c --- /dev/null +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -0,0 +1,562 @@ +import {expect} from 'chai'; +import {spec} from 'modules/outbrainBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Outbrain Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'outbrain', + 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({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed when bid contains native params', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should succeed when bid contains sizes', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'outbrain', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed with outbrain config', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + }) + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + 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') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + bidRequest.params.publisher.name = 'test-publisher' + bidRequest.params.publisher.domain = 'test-publisher.com' + bidRequest.params.bcat = ['bad-category'] + bidRequest.params.badv = ['bad-advertiser'] + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + expect(resData.site.publisher.name).to.equal('test-publisher') + expect(resData.site.publisher.domain).to.equal('test-publisher.com') + expect(resData.bcat).to.deep.equal(['bad-category']) + expect(resData.badv).to.deep.equal(['bad-advertiser']) + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + 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 native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.com' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + 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: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250, + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig({ + outbrain: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled with outbrain 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 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('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) 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 c8a636efb4c..f9941b41189 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -6,12 +6,13 @@ import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozon import * as utils from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; + /* NOTE - use firefox console to deep copy the objects to use here */ -var originalPropertyBag = {'lotameWasOverridden': 0, 'pageId': null}; +var originalPropertyBag = {'pageId': null}; var validBidRequests = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -21,7 +22,21 @@ var validBidRequests = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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 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' } @@ -36,7 +51,7 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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' }, @@ -49,11 +64,13 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c0', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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' } ]; +// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId' +// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -63,10 +80,76 @@ var validBidRequestsWithUserIdData = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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', - userId: {'pubcid': '12345678', 'id5id': 'ID5-someId', 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableid': 'parrableid123'} + userId: { + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'}, + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} + }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '12345678', + 'atype': 1 + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [{ + 'id': '1111tdid', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-someId', + 'atype': 1, + }] + }, + { + 'source': 'criteoId', + 'uids': [{ + 'id': '1111criteoId', + 'atype': 1, + }] + }, + { + 'source': 'idl_env', + 'uids': [{ + 'id': 'liverampId', + 'atype': 1, + }] + }, + { + 'source': 'lipb', + 'uids': [{ + 'id': {'lipbid': 'lipbidId123'}, + 'atype': 1, + }] + }, + { + 'source': 'parrableId', + 'uids': [{ + 'id': {'eid': '01.5678.parrableid'}, + 'atype': 1, + }] + } + ] + } ]; var validBidRequestsMinimal = [ @@ -91,7 +174,7 @@ var validBidRequestsNoSizes = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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 } } ] }, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; @@ -105,7 +188,7 @@ var validBidRequestsWithBannerMediaType = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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 } } ] }, mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -119,7 +202,7 @@ var validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, mediaTypes: {video: {mimes: ['video/mp4'], 'context': 'outstream', 'sizes': [640, 480], playerSize: [640, 480]}, native: {info: 'dummy data'}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -161,43 +244,21 @@ var validBidRequests1OutstreamVideo2020 = [ } } ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } + 'userId': { + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] } - } - }, - 'userId': { - 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + ] }, - 'userIdAsEids': [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', - 'atype': 1 - } - ] - } - ], 'mediaTypes': { 'video': { 'playerSize': [ @@ -272,32 +333,10 @@ var validBidderRequest1OutstreamVideo2020 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'userId': { - 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' }, 'userIdAsEids': [ @@ -370,7 +409,7 @@ var validBidderRequest = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -399,7 +438,7 @@ var bidderRequestWithFullGdpr = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -491,17 +530,6 @@ var bidderRequestWithPartialGdpr = { params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], - lotameData: { - 'Profile': { - 'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', - 'Audiences': { - 'Audience': [{'id': '99999', 'abbr': 'sports'}, { - 'id': '88888', - 'abbr': 'movie' - }, {'id': '77777', 'abbr': 'blogger'}] - } - } - }, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', @@ -982,29 +1010,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1069,29 +1075,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1165,29 +1149,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1252,29 +1214,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1588,8 +1528,7 @@ describe('ozone Adapter', function () { placementId: '1310000099', publisherId: '9876abcd12-3', siteId: '1234567890', - customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], - lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] }, siteId: 1234567890 } @@ -1883,26 +1822,11 @@ describe('ozone Adapter', function () { it('should not validate customParams - this is a renamed key', function () { expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); }); - - var xBadLotame = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'lotameData': 'this should be an object', - siteId: '1234567890' - } - }; - it('should not validate lotameData being sent', function () { - expect(spec.isBidRequestValid(xBadLotame)).to.equal(false); - }); - var xBadVideoContext2 = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'lotameData': {}, siteId: '1234567890' }, mediaTypes: { @@ -1937,35 +1861,6 @@ describe('ozone Adapter', function () { instreamVid.mediaTypes.video.context = 'instream'; expect(spec.isBidRequestValid(instreamVid)).to.equal(true); }); - // validate lotame override parameters - it('should validate lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - it('should validate missing lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate invalid lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123 "this ain\\t right!" eee'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate no lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); }); describe('buildRequests', function () { @@ -1989,19 +1884,27 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + + it('adds all parameters inside the ext object only - lightning', function () { + let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); + const request = spec.buildRequests(localBidReq, validBidderRequest.bidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(request).not.to.have.key('lotameData'); expect(request).not.to.have.key('customData'); }); it('ignores ozoneData in & after version 2.1.1', function () { - let validBidRequestsWithOzoneData = validBidRequests; + let validBidRequestsWithOzoneData = JSON.parse(JSON.stringify(validBidRequests)); validBidRequestsWithOzoneData[0].params.ozoneData = {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = spec.buildRequests(validBidRequestsWithOzoneData, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.ozoneData).to.be.undefined; expect(request).not.to.have.key('lotameData'); @@ -2018,7 +1921,7 @@ describe('ozone Adapter', function () { expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); - it('handles no ozone, lotame or custom data', function () { + it('handles no ozone or custom data', function () { const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); @@ -2080,6 +1983,7 @@ describe('ozone Adapter', function () { expect(payload.regs.ext.gdpr).to.equal(1); expect(payload.user.ext.consent).to.equal(consentString); }); + it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', 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; @@ -2116,15 +2020,15 @@ 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); const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; @@ -2136,66 +2040,209 @@ 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', // 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); const payload = JSON.parse(request.data); expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); - it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019)', function() { + it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest.bidderRequest); + /* + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'parrableId': {'eid': '01.5678.parrableid'} + */ + const payload = JSON.parse(request.data); expect(payload.user).to.exist; expect(payload.user.ext).to.exist; expect(payload.user.ext.eids).to.exist; - expect(payload.user.ext.eids[0]['source']).to.equal('pubcid'); + expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); - expect(payload.user.ext.eids[1]['source']).to.equal('pubcommon'); - expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); + 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'); - expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('critId123'); - expect(payload.user.ext.eids[4]['source']).to.equal('liveramp.com'); + 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('liveintent.com'); - expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[6]['source']).to.equal('parrable.com'); - expect(payload.user.ext.eids[6]['uids'][0]['id']).to.equal('parrableid123'); + 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 () { + spec.propertyBag.whitelabel = null; + let fakeOrigin = 'http://sometestendpoint'; + config.setConfig({'ozone': {'endpointOverride': {'origin': fakeOrigin}}}); + 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; + }); + + it('replaces the renderer url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeUrl = 'http://renderer.com'; + config.setConfig({'ozone': {'endpointOverride': {'rendererUrl': fakeUrl}}}); + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest1OutstreamVideo2020.bidderRequest); + const result = spec.interpretResponse(getCleanValidVideoResponse(), validBidderRequest1OutstreamVideo2020); + const bid = result[0]; + expect(bid.renderer).to.be.an.instanceOf(Renderer); + expect(bid.renderer.url).to.equal(fakeUrl); + 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; + config.setConfig({'ozone': {'kvpPrefix': 'test'}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone).to.haveOwnProperty('test_rw'); + config.setConfig({'ozone': {'kvpPrefix': null}}); + spec.propertyBag.whitelabel = null; }); + it('handles an alias ', function () { + spec.propertyBag.whitelabel = null; + config.setConfig({'lmc': {'kvpPrefix': 'test'}}); + let br = JSON.parse(JSON.stringify(validBidRequests)); + br[0]['bidder'] = 'lmc'; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.lmc).to.haveOwnProperty('test_rw'); + config.setConfig({'lmc': {'kvpPrefix': null}}); // I cant remove the key so set the value to null + spec.propertyBag.whitelabel = null; + }); it('should use oztestmode GET value if set', function() { + var specMock = utils.deepClone(spec); // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); 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); + // mock the getGetParametersAsObject function to simulate GET parameters : + 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); + // mock the getGetParametersAsObject function to simulate GET parameters : + 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() { + var specMock = utils.deepClone(spec); // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); 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'); }); - var specMock = utils.deepClone(spec); + it('should use GET values auction=dev & cookiesync=dev if set', function() { + // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: + 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'); + + // now mock the response from getGetParametersAsObject & do the request again + + 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 }; @@ -2206,6 +2253,7 @@ describe('ozone Adapter', function () { }); 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 }; @@ -2215,54 +2263,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); }); - it('should pick up the value of valid lotame override parameters when there is a lotame object', function () { - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eee'}; - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is an empty lotame object', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - nolotameBidReq[0].params.lotameData = {}; - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is NO "lotame" key at all', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - delete (nolotameBidReq[0].params['lotameData']); - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - spec.propertyBag = originalPropertyBag; // tidy up - }); - // NOTE - only one negative test case; - // you can't send invalid lotame params to buildRequests because 'validate' will have rejected them - it('should not use lotame override parameters if they dont exist', function () { - expect(spec.propertyBag.lotameWasOverridden).to.equal(0); - spec.getGetParametersAsObject = function() { - return {}; // no lotame override params - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.oz_lot_rw).to.equal(0); - }); - it('should pick up the config value of coppa & set it in the request', function () { config.setConfig({'coppa': true}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); @@ -2320,6 +2320,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); @@ -2331,6 +2397,33 @@ 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(); + }); }); describe('interpretResponse', function () { @@ -2356,11 +2449,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 () { @@ -2464,7 +2566,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 () { @@ -2511,7 +2613,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)); @@ -2521,7 +2623,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'); + expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-oz-2'); // change the bid values so a different second bid for an impid by the same bidder gets dropped validres = JSON.parse(JSON.stringify(multiResponse1)); validres.body.seatbid[0].bid[1].price = 1.1; @@ -2531,7 +2633,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'); }); }); @@ -2554,6 +2656,20 @@ 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() { + // get the cookie bag populated + 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() { + // 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'); + expect(result[0].url).to.include('usp_consent=&'); + }); }); describe('video object utils', function () { @@ -2609,6 +2725,14 @@ describe('ozone Adapter', function () { const result = playerSizeIsNestedArray(obj); expect(result).to.be.null; }); + it('should add oz_appnexus_dealid into ads request if dealid exists in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid[0].bid[0].dealid = '1234'; + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_dealid')).to.equal('1234'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_dealid', '')).to.equal(''); + }); }); describe('default size', function () { @@ -2660,44 +2784,6 @@ describe('ozone Adapter', function () { config.resetConfig(); }); }); - describe('makeLotameObjectFromOverride', function() { - it('should update an object with valid lotame data', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'Audiences': {'Audience': [{'id': 'aud1'}]}}} - ); - expect(result.Profile.Audiences.Audience).to.be.an('array'); - expect(result.Profile.Audiences.Audience[0]).to.be.an('object'); - expect(result.Profile.Audiences.Audience[0]).to.deep.include({'id': '12345', 'abbr': '12345'}); - }); - it('should return the original object if it seems weird', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let objLotameOriginal = {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'somethingstrange': [{'id': 'aud1'}]}}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - objLotameOriginal - ); - expect(result).to.equal(objLotameOriginal); - }); - }); - describe('lotameDataIsValid', function() { - it('should allow a valid minimum lotame object', function() { - let obj = {'Profile': {'pid': '', 'tpid': '', 'Audiences': {'Audience': []}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should allow a valid lotame object', function() { - let obj = {'Profile': {'pid': '12345', 'tpid': '45678', 'Audiences': {'Audience': [{'id': '1234', 'abbr': '567'}, {'id': '9999', 'abbr': '1111'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should disallow a lotame object without an Audience.id', function() { - let obj = {'Profile': {'tpid': '', 'pid': '', 'Audiences': {'Audience': [{'abbr': 'marktest'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.false; - }); - }); describe('getPageId', function() { it('should return the same Page ID for multiple calls', function () { let result = spec.getPageId(); @@ -2720,24 +2806,6 @@ describe('ozone Adapter', function () { expect(result).to.equal('outstream'); }); }); - describe('getLotameOverrideParams', function() { - it('should get 3 valid lotame params that exist in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(3); - }); - it('should get only 1 valid lotame param that exists in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'xoz_lotamepid': 'pid123', 'xoz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(1); - }); - }); describe('unpackVideoConfigIntoIABformat', function() { it('should correctly unpack a usual video config', function () { let mediaTypes = { 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 fdfd3042561..44118fb50de 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { newStorageManager } from 'src/storageManager.js'; @@ -11,15 +12,17 @@ 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' const P_CONFIG_MOCK = { name: 'parrableId', params: { - partner: 'parrable_test_partner_123,parrable_test_partner_456' + partners: 'parrable_test_partner_123,parrable_test_partner_456' } }; +const RESPONSE_HEADERS = { 'Content-Type': 'application/json' }; function getConfigMock() { return { @@ -56,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; } @@ -64,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' ); } @@ -73,114 +85,338 @@ function removeParrableCookie() { storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); } +function decodeBase64UrlSafe(encBase64) { + const DEC = { + '-': '+', + '_': '/', + '.': '=' + }; + return encBase64.replace(/[-_.]/g, (m) => DEC[m]); +} + describe('Parrable ID System', function() { - describe('parrableIdSystem.getId() callback', function() { - let logErrorStub; - let callbackSpy = sinon.spy(); + describe('parrableIdSystem.getId()', function() { + describe('response callback function', function() { + let logErrorStub; + let callbackSpy = sinon.spy(); + + beforeEach(function() { + logErrorStub = sinon.stub(utils, 'logError'); + callbackSpy.resetHistory(); + writeParrableCookie({ eid: P_COOKIE_EID }); + }); - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - callbackSpy.resetHistory(); - writeParrableCookie({ eid: P_COOKIE_EID }); - }); + afterEach(function() { + removeParrableCookie(); + logErrorStub.restore(); + }) - afterEach(function() { - removeParrableCookie(); - logErrorStub.restore(); - }) + it('creates xhr to Parrable that synchronizes the ID', function() { + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - it('creates xhr to Parrable that synchronizes the ID', function() { - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + getIdResult.callback(callbackSpy); - getIdResult.callback(callbackSpy); + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(queryParams.data)); + expect(getIdResult.callback).to.be.a('function'); + expect(request.url).to.contain('h.parrable.com'); - expect(getIdResult.callback).to.be.a('function'); - expect(request.url).to.contain('h.parrable.com'); + expect(queryParams).to.not.have.property('us_privacy'); + expect(data).to.deep.equal({ + eid: P_COOKIE_EID, + trackers: P_CONFIG_MOCK.params.partners.split(','), + url: getRefererInfo().referer, + prebidVersion: '$prebid.version$', + isIframe: true + }); + + server.requests[0].respond(200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({ eid: P_XHR_EID }) + ); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ + eid: P_XHR_EID + }); + + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID) + ); + }); - expect(queryParams).to.not.have.property('us_privacy'); - expect(data).to.deep.equal({ - eid: P_COOKIE_EID, - trackers: P_CONFIG_MOCK.params.partner.split(','), - url: getRefererInfo().referer + it('xhr passes the uspString to Parrable', function() { + let uspString = '1YNN'; + uspDataHandler.setConsentData(uspString); + parrableIdSubmodule.getId( + P_CONFIG_MOCK, + null, + null + ).callback(callbackSpy); + uspDataHandler.setConsentData(null); + expect(server.requests[0].url).to.contain('us_privacy=' + uspString); }); - server.requests[0].respond(200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ eid: P_XHR_EID }) - ); + it('xhr base64 safely encodes url data object', function() { + const urlSafeBase64EncodedData = '-_.'; + const btoaStub = sinon.stub(window, 'btoa').returns('+/='); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ - eid: P_XHR_EID + getIdResult.callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + expect(queryParams.data).to.equal(urlSafeBase64EncodedData); + btoaStub.restore(); }); - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID) - ); + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = parrableIdSubmodule.getId({ params: {partners: 'prebid'} }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('h.parrable.com'); + request.respond( + 503, + null, + 'Unavailable' + ); + expect(logErrorStub.calledOnce).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + }); }); - it('xhr passes the uspString to Parrable', function() { - let uspString = '1YNN'; - uspDataHandler.setConsentData(uspString); - parrableIdSubmodule.getId( - P_CONFIG_MOCK.params, - null, - null - ).callback(callbackSpy); - uspDataHandler.setConsentData(null); - expect(server.requests[0].url).to.contain('us_privacy=' + uspString); + describe('response id', function() { + it('provides the stored Parrable values if a cookie exists', function() { + writeParrableCookie({ eid: P_COOKIE_EID }); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); + removeParrableCookie(); + + expect(getIdResult.id).to.deep.equal({ + eid: P_COOKIE_EID + }); + }); + + it('provides the stored legacy Parrable ID values if cookies exist', function() { + let oldEid = '01.111.old-eid'; + let oldEidCookieName = '_parrable_eid'; + let oldOptoutCookieName = '_parrable_optout'; + + storage.setCookie(oldEidCookieName, oldEid); + storage.setCookie(oldOptoutCookieName, 'true'); + + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); + expect(getIdResult.id).to.deep.equal({ + eid: oldEid, + ibaOptout: true + }); + + // The ID system is expected to migrate old cookies to the new format + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') + ); + expect(storage.getCookie(oldEidCookieName)).to.equal(null); + expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); + removeParrableCookie(); + }); }); - it('should log an error and continue to callback if ajax request errors', function () { - let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({partner: 'prebid'}).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.contain('h.parrable.com'); - request.respond( - 503, - null, - 'Unavailable' - ); - expect(logErrorStub.calledOnce).to.be.true; - expect(callBackSpy.calledOnce).to.be.true; + describe('GDPR consent', () => { + let callbackSpy = sinon.spy(); + + const config = { + params: { + partner: 'partner' + } + }; + + const gdprConsentTestCases = [ + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: { gdpr: 1, gdpr_consent: 'expectedConsentString' } }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: { gdprApplies: true, consentString: undefined }, expected: { gdpr: 1, gdpr_consent: '' } }, + { consentData: { gdprApplies: 'yes', consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: undefined, expected: { gdpr: 0 } } + ]; + + gdprConsentTestCases.forEach((testCase, index) => { + it(`should call user sync url with the gdprConsent - case ${index}`, () => { + parrableIdSubmodule.getId(config, testCase.consentData).callback(callbackSpy); + + if (testCase.expected.gdpr === 1) { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.contain('gdpr_consent=' + testCase.expected.gdpr_consent); + } else { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.not.contain('gdpr_consent'); + } + }) + }); }); - }); - describe('parrableIdSystem.getId() id', function() { - it('provides the stored Parrable values if a cookie exists', function() { - writeParrableCookie({ eid: P_COOKIE_EID }); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); - removeParrableCookie(); + describe('third party cookie support', function () { + let logErrorStub; + let callbackSpy = sinon.spy(); - expect(getIdResult.id).to.deep.equal({ - eid: P_COOKIE_EID + 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) + ); + }); }); }); - it('provides the stored legacy Parrable ID values if cookies exist', function() { - let oldEid = '01.111.old-eid'; - let oldEidCookieName = '_parrable_eid'; - let oldOptoutCookieName = '_parrable_optout'; + describe('request-filter status', function () { + let logErrorStub; + let callbackSpy = sinon.spy(); - storage.setCookie(oldEidCookieName, oldEid); - storage.setCookie(oldOptoutCookieName, 'true'); + beforeEach(function() { + logErrorStub = sinon.stub(utils, 'logError'); + }); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); - expect(getIdResult.id).to.deep.equal({ - eid: oldEid, - ibaOptout: true + afterEach(function () { + callbackSpy.resetHistory(); + removeParrableCookie(); }); - // The ID system is expected to migrate old cookies to the new format - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') - ); - expect(storage.getCookie(oldEidCookieName)).to.equal(null); - expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); + 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); + }); + }); }); }); @@ -189,21 +425,224 @@ describe('Parrable ID System', function() { let eid = '01.123.4567890'; let parrableId = { eid, - ccpaOptout: true + ibaOptout: true }; expect(parrableIdSubmodule.decode(parrableId)).to.deep.equal({ - parrableid: eid + parrableId }); }); }); + describe('timezone filtering', function() { + before(function() { + sinon.stub(Intl, 'DateTimeFormat'); + }); + + after(function() { + Intl.DateTimeFormat.restore(); + }); + + it('permits an impression when no timezoneFilter is configured', function() { + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + } })).to.have.property('callback'); + }); + + it('permits an impression from a blocked timezone when a cookie exists', function() { + const blockedZone = 'Antarctica/South_Pole'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + writeParrableCookie({ eid: P_COOKIE_EID }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + } })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(false); + + removeParrableCookie(); + }) + + it('permits an impression from an allowed timezone', function() { + const allowedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + allowedZones: [ allowedZone ] + } + } })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + + it('permits an impression from a lower cased allowed timezone', function() { + const allowedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partner: 'prebid-test', + timezoneFilter: { + allowedZones: [ allowedZone.toLowerCase() ] + } + } })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + + it('permits an impression from a timezone that is not blocked', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: 'Iceland' }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + } })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + + it('does not permit an impression from a blocked timezone', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + } })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + + it('does not permit an impression from a lower cased blocked timezone', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partner: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone.toLowerCase() ] + } + } })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + + it('does not permit an impression from a blocked timezone even when also allowed', function() { + const timezone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: timezone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + allowedZones: [ timezone ], + blockedZones: [ timezone ] + } + } })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + }); + + describe('timezone offset filtering', function() { + before(function() { + sinon.stub(Date.prototype, 'getTimezoneOffset'); + }); + + afterEach(function() { + Date.prototype.getTimezoneOffset.reset(); + }) + + after(function() { + Date.prototype.getTimezoneOffset.restore(); + }); + + it('permits an impression from a blocked offset when a cookie exists', function() { + const blockedOffset = -4; + Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); + + writeParrableCookie({ eid: P_COOKIE_EID }); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + } })).to.have.property('callback'); + + removeParrableCookie(); + }); + + it('permits an impression from an allowed offset', function() { + const allowedOffset = -5; + Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + allowedOffsets: [ allowedOffset ] + } + } })).to.have.property('callback'); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('permits an impression from an offset that is not blocked', function() { + const allowedOffset = -5; + const blockedOffset = 5; + Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + }})).to.have.property('callback'); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('does not permit an impression from a blocked offset', function() { + const blockedOffset = -5; + Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + } })).to.equal(null); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('does not permit an impression from a blocked offset even when also allowed', function() { + const offset = -5; + Date.prototype.getTimezoneOffset.returns(offset * 60); + + expect(parrableIdSubmodule.getId({ params: { + partners: 'prebid-test', + timezoneFilter: { + allowedOffset: [ offset ], + blockedOffsets: [ offset ] + } + } })).to.equal(null); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + }); + describe('userId requestBids hook', function() { let adUnits; beforeEach(function() { adUnits = [getAdUnitMock()]; - writeParrableCookie({ eid: P_COOKIE_EID }); + writeParrableCookie({ eid: P_COOKIE_EID, ibaOptout: true }); setSubmoduleRegistry([parrableIdSubmodule]); init(config); config.setConfig(getConfigMock()); @@ -218,12 +657,94 @@ describe('Parrable ID System', function() { requestBidsHook(function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.parrableid'); - expect(bid.userId.parrableid).to.equal(P_COOKIE_EID); + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId.eid).to.equal(P_COOKIE_EID); + expect(bid.userId.parrableId.ibaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: P_COOKIE_EID, + atype: 1, + ext: { + ibaOptout: true + } + }] + }); }); }); done(); }, { adUnits }); }); + + it('supplies an optout reason when the EID is missing due to CCPA non-consent', function(done) { + // the ID system itself will not write a cookie with an EID when CCPA=true + writeParrableCookie({ ccpaOptout: true }); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId).to.not.have.property('eid'); + expect(bid.userId.parrableId.ccpaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: '', + atype: 1, + ext: { + ccpaOptout: true + } + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); + + describe('partners parsing', function () { + let callbackSpy = sinon.spy(); + + const partnersTestCase = [ + { + name: '"partners" as an array', + config: { params: { partners: ['parrable_test_partner_123', 'parrable_test_partner_456'] } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string list', + config: { params: { partners: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string', + config: { params: { partners: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + { + name: '"partner" as a string list', + config: { params: { partner: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partner" as string', + config: { params: { partner: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + ]; + partnersTestCase.forEach(testCase => { + it(`accepts config property ${testCase.name}`, () => { + parrableIdSubmodule.getId(testCase.config).callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); + + expect(data.trackers).to.deep.equal(testCase.expected); + }); + }); }); }); 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 new file mode 100644 index 00000000000..7cf6b66f839 --- /dev/null +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -0,0 +1,448 @@ +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 () { + it('should initalise and return true', function () { + expect(permutiveSubmodule.init()).to.equal(true) + }) + }) + + 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() + expect(getSegments(250)).to.deep.equal(data) + }) + it('should enforce max segments', function () { + const max = 1 + const segments = getSegments(max) + + for (const key in segments) { + expect(segments[key]).to.have.length(max) + } + }) + }) + + describe('Default segment targeting', function () { + it('sets segment targeting for Xandr', 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 === 'appnexus') { + expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + it('sets segment targeting for Magnite', 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 === 'rubicon') { + expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + it('sets segment targeting for Ozone', 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 === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + }) + + describe('Custom segment targeting', function () { + it('sets custom segment targeting for Magnite', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + config.params.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) + } + } + } + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + }) + + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', function () { + 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 === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Magnite', function () { + 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 === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Ozone', function () { + 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 === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', function () { + 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.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + + describe('Permutive on page', function () { + it('checks if Permutive is on page', function () { + expect(isPermutiveOnPage()).to.equal(false) + }) + }) + + describe('AC is enabled', function () { + it('checks if AC is enabled for Xandr', function () { + expect(isAcEnabled({ params: { acBidders: ['appnexus'] } }, 'appnexus')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdbfkvb'] } }, 'appnexus')).to.equal(false) + }) + it('checks if AC is enabled for Magnite', function () { + expect(isAcEnabled({ params: { acBidders: ['rubicon'] } }, 'rubicon')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdbfkb'] } }, 'rubicon')).to.equal(false) + }) + it('checks if AC is enabled for Ozone', function () { + expect(isAcEnabled({ params: { acBidders: ['ozone'] } }, 'ozone')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ozone')).to.equal(false) + }) + }) +}) + +function setLocalStorage (data) { + for (const key in data) { + storage.setDataInLocalStorage(key, JSON.stringify(data[key])) + } +} + +function removeLocalStorage (data) { + for (const key in data) { + storage.removeDataFromLocalStorage(key) + } +} + +function getConfig () { + return { + name: 'permutive', + waitForIt: true, + params: { + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], + maxSegs: 500 + } + } +} + +function transformedTargeting () { + const data = getTargetingData() + + return { + ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], + appnexus: data._papns, + rubicon: data._prubicons, + gam: data._pdfps + } +} + +function getTargetingData () { + return { + _pdfps: ['gam1', 'gam2'], + _prubicons: ['rubicon1', 'rubicon2'], + _papns: ['appnexus1', 'appnexus2'], + _psegs: ['1234', '1000001', '1000002'], + _ppam: ['ppam1', 'ppam2'], + _pcrprs: ['pcrprs1', 'pcrprs2'] + } +} + +function getAdUnits () { + const div1sizes = [ + [300, 250], + [300, 600] + ] + const div2sizes = [ + [728, 90], + [970, 250] + ] + return [ + { + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: div1sizes + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + keywords: { + test_kv: ['true'] + } + } + }, + { + bidder: 'rubicon', + params: { + accountId: '9840', + siteId: '123564', + zoneId: '583584', + inventory: { + area: ['home'] + }, + visitor: { + test_kv: ['true'] + } + } + }, + { + bidder: 'ozone', + params: { + publisherId: 'OZONEGMG0001', + siteId: '4204204209', + placementId: '0420420500', + customData: [ + { + settings: {}, + targeting: { + test_kv: ['true'] + } + } + ], + ozoneData: {} + } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } + } + ] + }, + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: div2sizes + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + keywords: { + test_kv: ['true'] + } + } + }, + { + bidder: 'ozone', + params: { + publisherId: 'OZONEGMG0001', + siteId: '4204204209', + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] + } + } + ] + } + ] +} 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 294ada6f988..a06d4dbcd68 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -15,7 +15,10 @@ let CONFIG = { bidders: ['appnexus'], timeout: 1000, cacheMarkup: 2, - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; const REQUEST = { @@ -26,6 +29,7 @@ const REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '0.30.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -80,6 +84,7 @@ const VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -110,6 +115,7 @@ const OUTSTREAM_VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -118,7 +124,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]] } }, @@ -137,7 +152,8 @@ const OUTSTREAM_VIDEO_REQUEST = { video: { playerSize: [640, 480], context: 'outstream', - mimes: ['video/mp4'] + mimes: ['video/mp4'], + skip: 1 } }, bids: [ @@ -151,18 +167,9 @@ 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, - }); - } - } + ] } - ] + ], }; let BID_REQUESTS; @@ -265,6 +272,9 @@ 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', } ] } } }, 'bidder': { @@ -497,16 +507,104 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should not add outstrean without renderer', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + 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 }); - config.setConfig({ s2sConfig: ortb2Config }); 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 () { + 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); + }); + + 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 () { @@ -519,15 +617,39 @@ describe('S2S Adapter', function () { }); it('adds gdpr consent information to ortb2 request depending on presence of module', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: 'abc123', + gdprApplies: true + }; - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.regs.ext.gdpr).is.equal(1); + expect(requestBid.user.ext.consent).is.equal('abc123'); + + config.resetConfig(); + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(server.requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + expect(requestBid.user).to.not.exist; + }); + + it('adds additional consent information to ortb2 request depending on presence of module', function () { + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: 'abc123', + addtlConsent: 'superduperconsent', gdprApplies: true }; @@ -536,6 +658,7 @@ describe('S2S Adapter', function () { expect(requestBid.regs.ext.gdpr).is.equal(1); expect(requestBid.user.ext.consent).is.equal('abc123'); + expect(requestBid.user.ext.ConsentedProvidersSettings.consented_providers).is.equal('superduperconsent'); config.resetConfig(); config.setConfig({ s2sConfig: CONFIG }); @@ -549,7 +672,7 @@ describe('S2S Adapter', function () { it('check gdpr info gets added into cookie_sync request: have consent data', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); @@ -561,7 +684,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(1); @@ -572,27 +698,30 @@ describe('S2S Adapter', function () { it('check gdpr info gets added into cookie_sync request: have consent data but gdprApplies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: 'xyz789abcc', gdprApplies: false }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); - it('checks gdpr info gets added to cookie_sync request: consent data unknown', function () { + it('checks gdpr info gets added to cookie_sync request: applies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); @@ -600,13 +729,16 @@ describe('S2S Adapter', function () { let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: undefined, - gdprApplies: undefined + gdprApplies: false }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.gdpr).is.undefined; + expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); }); @@ -617,9 +749,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1NYN'; @@ -640,13 +770,16 @@ describe('S2S Adapter', function () { it('is added to cookie_sync request when in bidRequest', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1YNN'; - adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, uspBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -661,9 +794,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let consentBidRequest = utils.deepClone(BID_REQUESTS); consentBidRequest[0].uspConsent = '1NYN'; @@ -691,7 +822,7 @@ describe('S2S Adapter', function () { it('is added to cookie_sync request when in bidRequest', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); let consentBidRequest = utils.deepClone(BID_REQUESTS); @@ -701,7 +832,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig + + adapter.callBids(s2sBidRequest, consentBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -712,40 +846,6 @@ describe('S2S Adapter', function () { }); }); - it('adds digitrust id is present and user is not optout', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; - config.setConfig(consentConfig); - - let digiTrustObj = { - privacy: { - optout: false - }, - id: 'testId', - keyv: 'testKeyV' - }; - - let digiTrustBidRequest = utils.deepClone(BID_REQUESTS); - digiTrustBidRequest[0].bids[0].userId = { digitrustid: { data: digiTrustObj } }; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.user.ext.digitrust).to.deep.equal({ - id: digiTrustObj.id, - keyv: digiTrustObj.keyv - }); - - digiTrustObj.privacy.optout = true; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - requestBid = JSON.parse(server.requests[1].requestBody); - - expect(requestBid.user && request.user.ext && requestBid.user.ext.digitrust).to.not.exist; - }); - it('adds device and app objects to request', function () { const _config = { s2sConfig: CONFIG, @@ -769,7 +869,9 @@ describe('S2S Adapter', function () { it('adds device and app objects to request for OpenRTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); const _config = { @@ -793,11 +895,8 @@ describe('S2S Adapter', function () { }); it('adds debugging value from storedAuctionResponse to OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' } }; @@ -813,13 +912,104 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('adds device.w and device.h even if the config lacks a device object', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + describe('price floors module', function () { + function runTest(expectedFloor, expectedCur) { + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[requestCount].requestBody); + expect(requestBid.imp[0].bidfloor).to.equal(expectedFloor); + expect(requestBid.imp[0].bidfloorcur).to.equal(expectedCur); + requestCount += 1; + } + + let getFloorResponse, requestCount; + beforeEach(function () { + getFloorResponse = {}; + requestCount = 0; + }); + + it('should NOT pass bidfloor and bidfloorcur when getFloor not present or returns invalid response', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + // if no get floor + runTest(undefined, undefined); + + // if getFloor returns empty object + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + runTest(undefined, undefined); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // if getFloor does not return number + getFloorResponse = {currency: 'EUR', floor: 'not a number'}; + runTest(undefined, undefined); + + // if getFloor does not return currency + getFloorResponse = {floor: 1.1}; + runTest(undefined, undefined); + }); + + it('should correctly pass bidfloor and bidfloorcur', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + runTest(1.23, 'USD'); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // returns non USD and number floor + getFloorResponse = {currency: 'EUR', floor: 0.85}; + runTest(0.85, 'EUR'); + }); + + it('should correctly pass adServerCurrency when set to getFloor not default', function () { + config.setConfig({ + s2sConfig: CONFIG, + currency: { adServerCurrency: 'JPY' }, + }); + + // we have to start requestCount at 1 because a conversion rates fetch occurs when adServerCur is not USD! + requestCount = 1; + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'JPY', floor: 97.2}; + runTest(97.2, 'JPY'); + // make sure getFloor was called with JPY + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'JPY', + }) + ).to.be.true; }); + }); + it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, app: { bundle: 'com.test.app' }, }; @@ -837,12 +1027,8 @@ describe('S2S Adapter', function () { }); it('adds native request for OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig + s2sConfig: CONFIG }; config.setConfig(_config); @@ -896,12 +1082,8 @@ describe('S2S Adapter', function () { }); it('adds site if app is not present', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, site: { publisher: { id: '1234', @@ -936,14 +1118,73 @@ describe('S2S Adapter', function () { }); it('adds appnexus aliases to request', function () { + config.setConfig({ s2sConfig: CONFIG }); + + const aliasBidder = { + bidder: 'brealtime', + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + brealtime: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } + }); + }); + + it('adds dynamic aliases to request', function () { + config.setConfig({ s2sConfig: CONFIG }); + + const alias = 'foobar'; + const aliasBidder = { + bidder: alias, + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + // TODO: stub this + $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + [alias]: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } + }); + }); + + it('skips pbs alias when skipPbsAliasing is enabled in adapter', function() { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); const aliasBidder = { - bidder: 'brealtime', - params: { placementId: '123456' } + bidder: 'mediafuse', + params: { aid: 123 } }; const request = utils.deepClone(REQUEST); @@ -955,48 +1196,52 @@ describe('S2S Adapter', function () { expect(requestBid.ext).to.deep.equal({ prebid: { - aliases: { - brealtime: 'appnexus' - }, auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); }); - it('adds dynamic aliases to request', function () { + it('skips dynamic aliases to request when skipPbsAliasing enabled', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); - const alias = 'foobar'; + const alias = 'foobar_1'; const aliasBidder = { bidder: alias, - params: { placementId: '123456' } + params: { aid: 123456 } }; const request = utils.deepClone(REQUEST); request.ad_units[0].bids = [aliasBidder]; // TODO: stub this - $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); + $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias, { skipPbsAliasing: true }); adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.deep.equal({ prebid: { - aliases: { - [alias]: 'appnexus' - }, auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); @@ -1004,7 +1249,9 @@ describe('S2S Adapter', function () { it('converts appnexus params to expected format for PBS', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }); config.setConfig({ s2sConfig: s2sConfig }); @@ -1033,13 +1280,16 @@ describe('S2S Adapter', function () { it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; cookieSyncConfig.userSyncLimit = 1; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + config.setConfig({ s2sConfig: cookieSyncConfig }); let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1049,11 +1299,14 @@ describe('S2S Adapter', function () { it('does not add limit to cooke_sync request if userSyncLimit is missing or 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1064,8 +1317,11 @@ describe('S2S Adapter', function () { config.resetConfig(); config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest2 = utils.deepClone(REQUEST); + s2sBidRequest2.s2sConfig = cookieSyncConfig; + bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest2, bidRequest, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1075,7 +1331,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig adapterOptions to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1088,8 +1343,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].ext.appnexus).to.haveOwnProperty('key'); expect(requestBid.imp[0].ext.appnexus.key).to.be.equal('value') @@ -1097,7 +1355,6 @@ describe('S2S Adapter', function () { describe('config site value is added to the oRTB request', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1109,6 +1366,9 @@ describe('S2S Adapter', function () { ip: '75.97.0.47' }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + it('and overrides publisher and page', function () { config.setConfig({ s2sConfig: s2sConfig, @@ -1119,7 +1379,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1137,7 +1398,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1149,10 +1411,7 @@ describe('S2S Adapter', function () { }); it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; + let consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); let userIdBidRequest = utils.deepClone(BID_REQUESTS); @@ -1160,12 +1419,18 @@ describe('S2S Adapter', function () { criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', tdid: 'abc123', pubcid: '1234', - parrableid: '01.1563917337.test-eid', + parrableId: { eid: '01.1563917337.test-eid' }, lipb: { lipbid: 'li-xyz', segments: ['segA', 'segB'] }, - idl_env: '0000-1111-2222-3333' + idl_env: '0000-1111-2222-3333', + id5id: { + uid: '11111', + ext: { + linkType: 'some-link-type' + } + } }; userIdBidRequest[0].bids[0].userIdAsEids = createEidsArray(userIdBidRequest[0].bids[0].userId); @@ -1186,16 +1451,16 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments.length).is.equal(2); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[0]).is.equal('segA'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].id).is.equal('11111'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].ext.linkType).is.equal('some-link-type'); // LiveRamp should exist expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: ['USD', 'GB', 'UK', 'AU'] }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1206,11 +1471,8 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: 'NZ' }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1221,9 +1483,7 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' is unset: ORTB should not define a \'cur\' property', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: s2sConfig }); + config.setConfig({ s2sConfig: CONFIG }); const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); @@ -1234,7 +1494,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includebidderkeys: false for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1248,7 +1507,11 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includebidderkeys'); @@ -1257,7 +1520,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includewinners: true for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1269,9 +1531,12 @@ describe('S2S Adapter', function () { device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includewinners'); @@ -1280,7 +1545,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig video.ext.prebid to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { foo: 'bar' } @@ -1291,13 +1555,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, foo: 'bar', targeting: { @@ -1309,7 +1576,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { targeting: { includewinners: false, @@ -1323,13 +1589,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, targeting: { includewinners: false, @@ -1340,7 +1609,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' @@ -1357,13 +1625,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' @@ -1400,6 +1671,52 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); }); + it('passes multibid array in request', function () { + const bidRequests = utils.deepClone(BID_REQUESTS); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + 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('does not set pbjs version in request if channel does exist in s2sConfig', () => { + 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); + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({test: 1}); + }); + it('passes first party data in request', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1428,27 +1745,124 @@ 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 => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + 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.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + 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 () { - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); + }); + + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].ortb2Imp = {}; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); + }); + + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); + }); + + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); + }); + }); + + describe('GAM ad unit config', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1457,34 +1871,34 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } + } } }; @@ -1493,18 +1907,21 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } + } } }; @@ -1513,12 +1930,40 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adslot'); - expect(parsedRequestBody.imp[0].ext.context.data.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); + describe('ext.prebid config', function () { + it('should send \"imp.ext.prebid.storedrequest.id\" if \"ortb2Imp.ext.prebid.storedrequest.id\" is set', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + const storedRequestId = 'my-id'; + bidRequest.ad_units[0].ortb2Imp = { + ext: { + prebid: { + storedrequest: { + id: storedRequestId + } + } + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.prebid.storedrequest.id'); + expect(parsedRequestBody.imp[0].ext.prebid.storedrequest.id).to.equal(storedRequestId); + }); + }); + describe('response handler', function () { beforeEach(function () { sinon.stub(utils, 'triggerPixel'); @@ -1667,10 +2112,7 @@ describe('S2S Adapter', function () { }; sinon.stub(adapterManager, 'getBidAdapter').returns(rubiconAdapter); - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1681,10 +2123,7 @@ describe('S2S Adapter', function () { }); it('handles OpenRTB responses and call BIDDER_DONE', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1703,6 +2142,9 @@ describe('S2S Adapter', function () { expect(response).to.have.property('meta'); expect(response.meta).to.have.property('advertiserDomains'); expect(response.meta.advertiserDomains[0]).to.equal('appnexus.com'); + expect(response.meta).to.have.property('dchain'); + expect(response.meta.dchain.ver).to.equal('1.0'); + expect(response.meta.dchain.nodes[0].asi).to.equal('magnite.com'); expect(response).to.not.have.property('vastUrl'); expect(response).to.not.have.property('videoCacheKey'); expect(response).to.have.property('ttl', 60); @@ -1710,12 +2152,13 @@ describe('S2S Adapter', function () { it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', defaultTtl: 30 }); - config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(events.emit); @@ -1727,11 +2170,16 @@ describe('S2S Adapter', function () { it('handles OpenRTB video responses', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); sinon.assert.calledOnce(addBidResponse); @@ -1746,7 +2194,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.cache.vastXml', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -1759,7 +2209,10 @@ describe('S2S Adapter', function () { } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1772,7 +2225,9 @@ describe('S2S Adapter', function () { it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -1784,7 +2239,11 @@ describe('S2S Adapter', function () { cacheResponse.seatbid.forEach(item => { item.bid[0].ext.prebid.targeting = targetingTestData }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1799,7 +2258,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -1810,7 +2271,11 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1823,7 +2288,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting with wurl', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -1837,7 +2304,10 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1847,7 +2317,9 @@ describe('S2S Adapter', function () { it('handles response cache from ext.prebid.targeting with wurl and removes invalid targeting', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); @@ -1863,7 +2335,11 @@ describe('S2S Adapter', function () { hb_bidid: '1234567890', } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1878,12 +2354,17 @@ describe('S2S Adapter', function () { it('add request property pbsBidId with ext.prebid.bidid value', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1899,11 +2380,16 @@ describe('S2S Adapter', function () { bidId: '123' }); const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } }); config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); sinon.assert.calledOnce(addBidResponse); @@ -1940,7 +2426,9 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }) }); }); @@ -2029,7 +2517,9 @@ describe('S2S Adapter', function () { bidders: ['appnexus'], timeout: 1000, adapter: 'prebidServer', - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; config.setConfig({ s2sConfig: options }); @@ -2042,7 +2532,9 @@ describe('S2S Adapter', function () { enabled: true, timeout: 1000, adapter: 's2s', - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + endpoint: { + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + } }; config.setConfig({ s2sConfig: options }); @@ -2089,8 +2581,29 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid.adnxs.com/pbs/v1/cookie_sync'); + expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction'}); + expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync'}); + 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); }); @@ -2110,8 +2623,8 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['rubicon']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig).to.have.property('endpoint', 'https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(vendorConfig).to.have.property('syncEndpoint', 'https://prebid-server.rubiconproject.com/cookie_sync'); + expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}); + expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2130,8 +2643,8 @@ describe('S2S Adapter', function () { 'bidders': ['rubicon'], 'defaultVendor': 'rubicon', 'enabled': true, - 'endpoint': 'https://prebid-server.rubiconproject.com/openrtb2/auction', - 'syncEndpoint': 'https://prebid-server.rubiconproject.com/cookie_sync', + 'endpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, + 'syncEndpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, 'timeout': 750 }) }); @@ -2152,8 +2665,8 @@ describe('S2S Adapter', function () { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', - endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - syncEndpoint: 'https://prebid-server.rubiconproject.com/cookie_sync', + endpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, + syncEndpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, }) }); @@ -2201,13 +2714,14 @@ describe('S2S Adapter', function () { // Add syncEndpoint so that the request goes to the User Sync endpoint // Modify the bidders property to include an alias for Rubicon adapter - s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; s2sConfig.bidders = ['appnexus', 'rubicon-alias']; - const request = utils.deepClone(REQUEST); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; // Add another bidder, `rubicon-alias` - request.ad_units[0].bids.push({ + s2sBidRequest.ad_units[0].bids.push({ bidder: 'rubicon-alias', params: { accoundId: 14062, @@ -2219,8 +2733,6 @@ describe('S2S Adapter', function () { // create an alias for the Rubicon Bid Adapter adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); - config.setConfig({ s2sConfig }); - const bidRequest = utils.deepClone(BID_REQUESTS); bidRequest.push({ 'bidderCode': 'rubicon-alias', @@ -2264,10 +2776,52 @@ describe('S2S Adapter', function () { 'src': 's2s' }); - adapter.callBids(request, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); }); + + it('should add cooperative sync flag to cookie_sync request if property is present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.coopSync = false; + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.equal(false); + }); + + it('should not add cooperative sync flag to cookie_sync request if property is not present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + 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 e87be40314c..e504eced868 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -1,6 +1,10 @@ -import prebidmanagerAnalytics from 'modules/prebidmanagerAnalyticsAdapter.js'; +import prebidmanagerAnalytics, { + storage +} from 'modules/prebidmanagerAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + let events = require('src/events'); let constants = require('src/constants.json'); @@ -103,19 +107,18 @@ describe('Prebid Manager Analytics Adapter', function () { }); describe('build utm tag data', function () { + let getDataFromLocalStorageStub; + this.timeout(4000) beforeEach(function () { - localStorage.setItem('pm_utm_source', 'utm_source'); - localStorage.setItem('pm_utm_medium', 'utm_medium'); - localStorage.setItem('pm_utm_campaign', 'utm_camp'); - localStorage.setItem('pm_utm_term', ''); - localStorage.setItem('pm_utm_content', ''); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorageStub.withArgs('pm_utm_source').returns('utm_source'); + getDataFromLocalStorageStub.withArgs('pm_utm_medium').returns('utm_medium'); + getDataFromLocalStorageStub.withArgs('pm_utm_campaign').returns('utm_camp'); + getDataFromLocalStorageStub.withArgs('pm_utm_term').returns(''); + getDataFromLocalStorageStub.withArgs('pm_utm_content').returns(''); }); afterEach(function () { - localStorage.removeItem('pm_utm_source'); - localStorage.removeItem('pm_utm_medium'); - localStorage.removeItem('pm_utm_campaign'); - localStorage.removeItem('pm_utm_term'); - localStorage.removeItem('pm_utm_content'); + getDataFromLocalStorageStub.restore(); prebidmanagerAnalytics.disableAnalytics() }); it('should build utm data from local storage', function () { @@ -135,4 +138,23 @@ describe('Prebid Manager Analytics Adapter', function () { expect(pmEvents.utmTags.utm_content).to.equal(''); }); }); + + describe('build page info', function () { + afterEach(function () { + prebidmanagerAnalytics.disableAnalytics() + }); + it('should build page info', function () { + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + + expect(pmEvents.pageInfo.domain).to.equal(window.location.hostname); + expect(pmEvents.pageInfo.referrerDomain).to.equal(utils.parseUrl(document.referrer).hostname); + }); + }); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 9d35554c27f..b3105dafc39 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -15,6 +15,7 @@ import { allowedFields } from 'modules/priceFloors.js'; import events from 'src/events.js'; +import * as mockGpt from '../integration/faker/googletag.js'; describe('the price floors module', function () { let logErrorSpy; @@ -23,6 +24,38 @@ describe('the price floors module', function () { let clock; const basicFloorData = { modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + 'video': 5.0, + '*': 2.5 + } + }; + const basicFloorDataHigh = { + floorMin: 7.0, + modelVersion: 'basic model', + modelWeight: 10, + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + 'video': 5.0, + '*': 2.5 + } + }; + const basicFloorDataLow = { + floorMin: 2.3, + modelVersion: 'basic model', + modelWeight: 10, currency: 'USD', schema: { delimiter: '|', @@ -46,6 +79,32 @@ describe('the price floors module', function () { }, data: basicFloorData } + const minFloorConfigHigh = { + enabled: true, + auctionDelay: 0, + floorMin: 7, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: basicFloorDataHigh + } + const minFloorConfigLow = { + enabled: true, + auctionDelay: 0, + floorMin: 2.3, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: basicFloorDataLow + } const basicBidRequest = { bidder: 'rubicon', adUnitCode: 'test_div_1', @@ -130,6 +189,8 @@ describe('the price floors module', function () { let resultingData = getFloorsDataForAuction(inputFloorData, 'test_div_1'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, currency: 'USD', schema: { delimiter: '|', @@ -147,6 +208,8 @@ describe('the price floors module', function () { resultingData = getFloorsDataForAuction(inputFloorData, 'this_is_a_div'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, currency: 'USD', schema: { delimiter: '^', @@ -165,22 +228,66 @@ describe('the price floors module', function () { it('selects the right floor for different mediaTypes', function () { // banner with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.0, matchingFloor: 1.0, matchingData: 'banner', matchingRule: 'banner' }); // video with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'video', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 5.0, matchingFloor: 5.0, matchingData: 'video', matchingRule: 'video' }); // native (not in the rule list) with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'native', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 2.5, matchingFloor: 2.5, matchingData: 'native', matchingRule: '*' }); + // banner with floorMin higher than matching rule + handleSetFloorsConfig({ + ...minFloorConfigHigh + }); + expect(getFirstMatchingFloor({...basicFloorDataHigh}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 7, + floorRuleValue: 1.0, + matchingFloor: 7, + matchingData: 'banner', + matchingRule: 'banner' + }); + // banner with floorMin higher than matching rule + handleSetFloorsConfig({ + ...minFloorConfigLow + }); + expect(getFirstMatchingFloor({...basicFloorDataLow}, basicBidRequest, {mediaType: 'video', size: '*'})).to.deep.equal({ + floorMin: 2.3, + floorRuleValue: 5, + matchingFloor: 5, + matchingData: 'video', + matchingRule: 'video' + }); + }); + it('does not alter cached matched input if conversion occurs', function () { + let inputData = {...basicFloorData}; + [0.2, 0.4, 0.6, 0.8].forEach(modifier => { + let result = getFirstMatchingFloor(inputData, basicBidRequest, {mediaType: 'banner', size: '*'}); + // result should always be the same + expect(result).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.0, + matchingFloor: 1.0, + matchingData: 'banner', + matchingRule: 'banner' + }); + // make sure a post retrieval adjustment does not alter the cached floor + result.matchingFloor = result.matchingFloor * modifier; + }); }); it('selects the right floor for different sizes', function () { let inputFloorData = { @@ -199,24 +306,32 @@ describe('the price floors module', function () { } // banner with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: '300x250', matchingRule: '300x250' }); // video with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: '300x250', matchingRule: '300x250' }); // native (not in the rule list) with 300x600 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'native', size: [600, 300]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 4.4, matchingFloor: 4.4, matchingData: '600x300', matchingRule: '600x300' }); // n/a mediaType with a size not in file should go to catch all expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: undefined, size: [1, 1]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 5.5, matchingFloor: 5.5, matchingData: '1x1', matchingRule: '*' @@ -240,12 +355,16 @@ describe('the price floors module', function () { }; // banner with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: 'test_div_1^banner^300x250', matchingRule: 'test_div_1^banner^300x250' }); // video with 300x250 size -> No matching rule so should use default expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0.5, matchingFloor: 0.5, matchingData: 'test_div_1^video^300x250', matchingRule: undefined @@ -253,6 +372,8 @@ describe('the price floors module', function () { // remove default and should still return the same floor as above since matches are cached delete inputFloorData.default; expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0.5, matchingFloor: 0.5, matchingData: 'test_div_1^video^300x250', matchingRule: undefined @@ -260,6 +381,8 @@ describe('the price floors module', function () { // update adUnitCode to test_div_2 with weird other params let newBidRequest = { ...basicBidRequest, adUnitCode: 'test_div_2' } expect(getFirstMatchingFloor(inputFloorData, newBidRequest, {mediaType: 'badmediatype', size: [900, 900]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 3.3, matchingFloor: 3.3, matchingData: 'test_div_2^badmediatype^900x900', matchingRule: 'test_div_2^*^*' @@ -276,6 +399,84 @@ describe('the price floors module', function () { matchingFloor: 5.0 }); }); + describe('with gpt enabled', function () { + let gptFloorData; + 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' + }); + }); + afterEach(function () { + // reset it so no lingering stuff from other test specs + mockGpt.reset(); + }); + 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 bidRequest and does not call the slotMatching', function () { + const newBidRequest1 = { ...basicBidRequest }; + utils.deepSetValue(newBidRequest1, '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' }; + utils.deepSetValue(newBidRequest2, '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; @@ -313,10 +514,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(false, { skipped: true, + floorMin: undefined, modelVersion: undefined, - location: undefined, + modelWeight: undefined, + modelTimestamp: undefined, + location: 'noData', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should use adUnit level data if not setConfig or fetch has occured', function () { @@ -345,21 +550,120 @@ describe('the price floors module', function () { runStandardAuction([adUnitWithFloors1, adUnitWithFloors2]); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'adUnit Model Version', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should use adUnit level data and minFloor should be set', function () { + handleSetFloorsConfig({ + ...minFloorConfigHigh, + data: undefined + }); + // attach floor data onto an adUnit and run an auction + let adUnitWithFloors1 = { + ...getAdUnitMock('adUnit-Div-1'), + floors: { + ...basicFloorData, + modelVersion: 'adUnit Model Version', // change the model name + } + }; + let adUnitWithFloors2 = { + ...getAdUnitMock('adUnit-Div-2'), + floors: { + ...basicFloorData, + values: { + 'banner': 5.0, + '*': 10.4 + } + } + }; + runStandardAuction([adUnitWithFloors1, adUnitWithFloors2]); + validateBidRequests(true, { + skipped: false, + modelVersion: 'adUnit Model Version', + modelWeight: 10, + modelTimestamp: 1606772895, + location: 'adUnit', + skipRate: 0, + floorMin: 7, + fetchStatus: undefined, + floorProvider: undefined }); }); it('bidRequests should have getFloor function and flooring meta data when setConfig occurs', function () { - handleSetFloorsConfig({...basicFloorConfig}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider'}); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + }); + it('should pick the right floorProvider', function () { + let inputFloors = { + ...basicFloorConfig, + floorProvider: 'providerA', + data: { + ...basicFloorData, + floorProvider: 'providerB', + } + }; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'providerB' + }); + + // if not at data level take top level + delete inputFloors.data.floorProvider; + handleSetFloorsConfig(inputFloors); runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'providerA' + }); + + // if none should be undefined + delete inputFloors.floorProvider; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined }); }); it('should take the right skipRate depending on input', function () { @@ -377,10 +681,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 50, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); // if that does not exist uses topLevel skipRate setting @@ -389,10 +697,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 10, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); // if that is not there defaults to zero @@ -401,15 +713,20 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should randomly pick a model if floorsSchemaVersion is 2', function () { let inputFloors = { ...basicFloorConfig, + floorProvider: 'floorprovider', data: { floorsSchemaVersion: 2, currency: 'USD', @@ -462,10 +779,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-1', + modelWeight: 10, + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); // 11 - 50 should use second model @@ -473,10 +794,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-2', + modelWeight: 40, + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); // 51 - 100 should use third model @@ -484,10 +809,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-3', + modelWeight: 50, + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); }); it('should not overwrite previous data object if the new one is bad', function () { @@ -511,10 +840,14 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should dynamically add new schema fileds and functions if added via setConfig', function () { @@ -588,10 +921,14 @@ describe('the price floors module', function () { // the exposedAdUnits should be from the fetch not setConfig level data validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: 'timeout' + fetchStatus: 'timeout', + floorProvider: undefined }); fakeFloorProvider.respond(); }); @@ -604,7 +941,7 @@ describe('the price floors module', function () { fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); // run setConfig indicating fetch - handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); // floor provider should be called expect(fakeFloorProvider.requests.length).to.equal(1); @@ -624,10 +961,53 @@ describe('the price floors module', function () { // and fetchStatus is success since fetch worked validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: 'floorprovider' + }); + }); + it('it should correctly overwrite floorProvider with fetch provider', function () { + // init the fake server with response stuff + let fetchFloorData = { + ...basicFloorData, + floorProvider: 'floorProviderD', // change the floor provider + modelVersion: 'fetch model name', // change the model name + }; + fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorproviderC', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // floor provider should be called + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // make the fetch respond + fakeFloorProvider.respond(); + + // the exposedAdUnits should be from the fetch not setConfig level data + // and fetchStatus is success since fetch worked + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'fetch model name', + modelWeight: 10, + modelTimestamp: 1606772895, + location: 'fetch', + skipRate: 0, + fetchStatus: 'success', + floorProvider: 'floorProviderD' }); }); it('it should correctly overwrite skipRate with fetch skipRate', function () { @@ -642,7 +1022,7 @@ describe('the price floors module', function () { fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); // run setConfig indicating fetch - handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); // floor provider should be called expect(fakeFloorProvider.requests.length).to.equal(1); @@ -662,10 +1042,14 @@ describe('the price floors module', function () { // and fetchStatus is success since fetch worked validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'fetch', skipRate: 95, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: 'floorprovider' }); }); it('Should not break if floor provider returns 404', function () { @@ -682,10 +1066,14 @@ describe('the price floors module', function () { // and fetch failed is true validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: 'error' + fetchStatus: 'error', + floorProvider: undefined }); }); it('Should not break if floor provider returns non json', function () { @@ -704,10 +1092,14 @@ describe('the price floors module', function () { // and fetchStatus is 'success' but location is setConfig since it had bad data validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: undefined }); }); it('should handle not using fetch correctly', function () { @@ -1003,6 +1395,74 @@ describe('the price floors module', function () { floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) }); }); + + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.5; + }, + }, + standard: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.75; + }, + } + }; + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus' + }; + + // the conversion should be what the bidder would need to return in order to match the actual floor + // rubicon + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // a 2.0 bid after rubicons cpm adjustment would be 1.0 and thus is the floor after adjust + }); + + // appnexus + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) + }); + }); + + it('should work when cpmAdjust function uses bid object', function () { + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.75; + }, + } + }; + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus' + }; + + // the conversion should be what the bidder would need to return in order to match the actual floor + // rubicon + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // a 2.0 bid after rubicons cpm adjustment would be 1.0 and thus is the floor after adjust + }); + + // appnexus + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) + }); + }); it('should correctly pick the right attributes if * is passed in and context can be assumed', function () { let inputBidReq = { bidder: 'rubicon', @@ -1158,6 +1618,7 @@ describe('the price floors module', function () { runBidResponse(); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ + floorRuleValue: 0.3, floorValue: 0.3, floorCurrency: 'USD', floorRule: 'banner', @@ -1195,6 +1656,7 @@ describe('the price floors module', function () { expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ floorValue: 0.5, + floorRuleValue: 0.5, floorCurrency: 'USD', floorRule: 'banner|300x250', cpmAfterAdjustments: 0.5, @@ -1221,6 +1683,7 @@ describe('the price floors module', function () { }); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ + floorRuleValue: 5.5, floorValue: 5.5, floorCurrency: 'USD', floorRule: 'video|*', diff --git a/test/spec/modules/projectLimeLightBidAdapter_spec.js b/test/spec/modules/projectLimeLightBidAdapter_spec.js deleted file mode 100644 index 3ffc017f177..00000000000 --- a/test/spec/modules/projectLimeLightBidAdapter_spec.js +++ /dev/null @@ -1,170 +0,0 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/projectLimeLightBidAdapter.js'; - -describe('ProjectLimeLightAdapter', 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' - }; - - 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://ads.project-limelight.com/hb'); - }); - 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'); - let adUnits = data['adUnits']; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - 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 empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.adUnits).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); - }); - }); -}); diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 410c3c59fb6..084c533b5b5 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,55 +1,66 @@ 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=='; const bidderRequest = { - 'bidderCode': BIDDER_CODE, - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', - 'bidderRequestId': '10edf38ec1a719', - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': 'CONSENT_STRING', - 'vendorData': { - 'vendorConsents': { - '418': true - } - } - } + bidderCode: BIDDER_CODE, + auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', + bidderRequestId: '10edf38ec1a719', + gdprConsent: { + apiVersion: 2, + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + 418: true, + }, + }, + }, + }, }; let bid = { sizes: [[300, 600]], params: { website: 'example.fr', - language: 'fr' + language: 'fr', + }, + ortb2: { + user: { ext: { data: { segments: [], contextual_categories: {} } } }, }, auctionId: 442133079, bidId: 464646969, - transactionId: 511916005 + transactionId: 511916005, }; describe('isBidRequestValid', function () { it('it should be true if required params are presents and there is no info in the local storage', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function() { + it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 1) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) - expect(spec.isBidRequestValid(bid)).to.equal(false); + date.setMinutes(date.getMinutes() - 1); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function() { + it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 10) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) + date.setMinutes(date.getMinutes() - 10); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); expect(spec.isBidRequestValid(bid)).to.equal(true); }); }); - describe('buildRequests', function () { - const url = 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi'; - const request = spec.buildRequests([bid], bidderRequest); + const url = { + cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', + cookieLess: + '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'); expect(request.method).to.exist; @@ -59,16 +70,45 @@ describe('ProxistoreBidAdapter', function () { it('request method should be POST', function () { expect(request.method).to.equal('POST'); }); - it('should contain a valid url', function () { - expect(request.url).equal(url); - }); it('should have the value consentGiven to true bc we have 418 in the vendor list', function () { const data = JSON.parse(request.data); - - expect(data.gdpr.consentString).equal(bidderRequest.gdprConsent.consentString); + expect(data.gdpr.consentString).equal( + bidderRequest.gdprConsent.consentString + ); expect(data.gdpr.applies).to.be.true; expect(data.gdpr.consentGiven).to.be.true; }); + it('should contain a valid url', function () { + // has gdpr consent + expect(request.url).equal(url.cookieBase); + // doens't have gpdr consent + bidderRequest.gdprConsent.vendorData = null; + + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + + // api v2 + bidderRequest.gdprConsent = { + gdprApplies: true, + allowAuctionWithoutConsent: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + 418: true, + }, + }, + }, + apiVersion: 2, + }; + // has gdpr consent + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieBase); + + bidderRequest.gdprConsent.vendorData.vendor = {}; + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + }); it('should have a property a length of bids equal to one if there is only one bid', function () { const data = JSON.parse(request.data); expect(data.hasOwnProperty('bids')).to.be.true; @@ -77,51 +117,53 @@ describe('ProxistoreBidAdapter', function () { expect(data.bids[0].hasOwnProperty('id')).to.be.true; expect(data.bids[0].sizes).to.be.an('array'); }); - }); + it('should correctly set bidfloor on imp when getfloor in scope', function () { + let data = JSON.parse(request.data); + expect(data.bids[0].floor).to.be.null; + 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 }; + }; + req = spec.buildRequests([bid], bidderRequest); + data = JSON.parse(req.data); + expect(data.bids[0].floor).to.be.null; + }); + }); describe('interpretResponse', function () { - const responses = { - body: - [{ + const emptyResponseParam = { body: [] }; + const fakeResponseParam = { + body: [ + { + ad: '', cpm: 6.25, - creativeId: '48fd47c9-ce35-4fda-804b-17e16c8c36ac', + creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', currency: 'EUR', - dealId: '2019-10_e3ecad8e-d07a-4c90-ad46-cd0f306c8960', + dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', height: 600, netRevenue: true, - requestId: '923756713', + requestId: '3543724f2a033c9', + segments: [], ttl: 10, vastUrl: null, vastXml: null, width: 300, - }] + }, + ], }; - const badResponse = { body: [] }; - const interpretedResponse = spec.interpretResponse(responses, bid)[0]; - it('should send an empty array if body is empty', function () { - expect(spec.interpretResponse(badResponse, bid)).to.be.an('array'); - expect(spec.interpretResponse(badResponse, bid).length).equal(0); - }); - it('should interpret the response correctly if it is valid', function () { - expect(interpretedResponse.cpm).equal(6.25); - expect(interpretedResponse.creativeId).equal('48fd47c9-ce35-4fda-804b-17e16c8c36ac'); - expect(interpretedResponse.currency).equal('EUR'); - expect(interpretedResponse.height).equal(600); - expect(interpretedResponse.width).equal(300); - expect(interpretedResponse.requestId).equal('923756713'); - expect(interpretedResponse.netRevenue).to.be.true; - expect(interpretedResponse.netRevenue).to.be.true; - }); - it('should have a value in the local storage if the response is empty', function() { - spec.interpretResponse(badResponse, bid); - expect(localStorage.getItem(`PX_NoAds_${bid.params.website}`)).to.be.string; - }); - }); - describe('interpretResponse', function () { - it('should aways return an empty array', function () { - expect(spec.getUserSyncs()).to.be.an('array'); - expect(spec.getUserSyncs().length).equal(0); + it('should always return an array', function () { + let response = spec.interpretResponse(emptyResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(0); + response = spec.interpretResponse(fakeResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(1); }); }); }); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index ab0ef2adc51..a46ff26c4b8 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -234,7 +234,7 @@ describe('Publisher Common ID', function () { }); }); - it('disable auto create', function() { + it.skip('disable auto create', function() { setConfig({ create: false }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index c510be99402..6568f7aa782 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; import { spec } from 'modules/pubgeniusBidAdapter.js'; -import { deepClone, parseQueryStringParameters } from 'src/utils.js'; import { config } from 'src/config.js'; +import { VIDEO } from 'src/mediaTypes.js'; +import { deepClone, parseQueryStringParameters } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; const { @@ -23,8 +24,8 @@ describe('pubGENIUS adapter', () => { }); describe('supportedMediaTypes', () => { - it('should contain only banner', () => { - expect(supportedMediaTypes).to.deep.equal(['banner']); + it('should contain banner and video', () => { + expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); }); }); @@ -77,6 +78,51 @@ describe('pubGENIUS adapter', () => { expect(isBidRequestValid(bid)).to.be.false; }); + + it('should return false without banner or video', () => { + bid.mediaTypes = {}; + + expect(isBidRequestValid(bid)).to.be.false; + }); + + it('should return true with valid video media type', () => { + bid.mediaTypes = { + video: { + context: 'instream', + playerSize: [[100, 100]], + mimes: ['video/mp4'], + protocols: [1], + }, + }; + + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return true with valid video params', () => { + bid.params.video = { + placement: 1, + w: 200, + h: 200, + mimes: ['video/mp4'], + protocols: [1], + }; + + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return false without video protocols', () => { + bid.mediaTypes = { + video: { + context: 'instream', + playerSize: [[100, 100]], + }, + }; + bid.params.video = { + mimes: ['video/mp4'], + }; + + expect(isBidRequestValid(bid)).to.be.false; + }); }); describe('buildRequests', () => { @@ -122,6 +168,7 @@ describe('pubGENIUS adapter', () => { bidderCode: 'pubgenius', bidderRequestId: 'fakebidderrequestid', refererInfo: {}, + timeout: 1200, }; expectedRequest = { @@ -142,14 +189,14 @@ describe('pubGENIUS adapter', () => { tmax: 1200, ext: { pbadapter: { - version: '1.0.0', + version: '1.1.0', }, }, }, }; config.setConfig({ - bidderTimeout: 1200, + bidderTimeout: 1000, pageUrl: undefined, coppa: undefined, }); @@ -178,8 +225,8 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest, bidRequest1], bidderRequest)).to.deep.equal(expectedRequest); }); - it('should take bid floor in bidder params', () => { - bidRequest.params.bidFloor = 0.5; + it('should take bid floor from getFloor interface', () => { + bidRequest.getFloor = () => ({ floor: 0.5, currency: 'USD' }); expectedRequest.data.imp[0].bidfloor = 0.5; expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); @@ -200,6 +247,14 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); }); + it('should use canonical URL over referer in refererInfo', () => { + bidderRequest.refererInfo.canonicalUrl = 'http://pageurl.org'; + bidderRequest.refererInfo.referer = 'http://referer.org'; + expectedRequest.data.site = { page: 'http://pageurl.org' }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + it('should take gdprConsent when GDPR does not apply', () => { bidderRequest.gdprConsent = { gdprApplies: false, @@ -248,7 +303,7 @@ describe('pubGENIUS adapter', () => { } ] }; - bidderRequest.schain = deepClone(schain); + bidRequest.schain = deepClone(schain); expectedRequest.data.source = { ext: { schain: deepClone(schain) }, }; @@ -305,6 +360,47 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); }); + + it('should build video imp', () => { + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [[200, 100]], + mimes: ['video/mp4'], + protocols: [2, 3], + api: [1, 2], + playbackmethod: [3, 4], + maxduration: 10, + linearity: 1, + }, + }; + bidRequest.params.video = { + minduration: 5, + maxduration: 100, + skip: 1, + skipafter: 1, + startdelay: -1, + }; + + delete expectedRequest.data.imp[0].banner; + expectedRequest.data.imp[0].video = { + mimes: ['video/mp4'], + minduration: 5, + maxduration: 100, + protocols: [2, 3], + w: 200, + h: 100, + startdelay: -1, + placement: 1, + skip: 1, + skipafter: 1, + playbackmethod: [3, 4], + api: [1, 2], + linearity: 1, + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); }); describe('interpretResponse', () => { @@ -361,6 +457,29 @@ describe('pubGENIUS adapter', () => { it('should interpret no bids', () => { expect(interpretResponse({ body: {} })).to.deep.equal([]); }); + + it('should interpret video response', () => { + serverResponse.body.seatbid[0].bid[0] = { + ...serverResponse.body.seatbid[0].bid[0], + nurl: 'http://vasturl/cache?id=x', + ext: { + pbadapter: { + mediaType: 'video', + cacheKey: 'x', + }, + }, + }; + + delete expectedBidResponse.ad; + expectedBidResponse = { + ...expectedBidResponse, + vastUrl: 'http://vasturl/cache?id=x', + vastXml: 'fake_creative', + mediaType: VIDEO, + }; + + expect(interpretResponse(serverResponse)).to.deep.equal([expectedBidResponse]); + }); }); describe('getUserSyncs', () => { diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js new file mode 100644 index 00000000000..326285709f3 --- /dev/null +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -0,0 +1,136 @@ +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'; + +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({publink: 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('ignore expired cookie', () => { + storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify({publink: 'value', exp: Date.now() - 60 * 60 * 24 * 1000})); + const result = publinkIdSubmodule.getId(); + expect(result.id).to.be.undefined; + 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); + }); + }); + + 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: 'hashedemailvalue'}}; + const consentData = {gdprApplies: 1, consentString: 'myconsentstring'}; + let submoduleCallback = publinkIdSubmodule.getId(config, consentData).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$&gdpr=1&gdpr_consent=myconsentstring'); + + 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: 'hashedemailvalue'}}; + let submoduleCallback = publinkIdSubmodule.getId(config).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$'); + + request.respond(204, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.calledOnce).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: 'hashedemailvalue'}}; + let submoduleCallback = publinkIdSubmodule.getId(config).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$&us_privacy=1YNN'); + + 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 537ba4483cf..c6496ee7fe1 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 { @@ -11,6 +12,12 @@ let events = require('src/events'); let ajax = require('src/ajax'); let utils = require('src/utils'); +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; +const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const setUANull = () => { window.navigator.__defineGetter__('userAgent', function () { return null }) }; + const { EVENTS: { AUCTION_INIT, @@ -247,6 +254,7 @@ describe('pubmatic analytics adapter', function () { let clock; beforeEach(function () { + setUADefault(); sandbox = sinon.sandbox.create(); xhr = sandbox.useFakeXMLHttpRequest(); @@ -295,6 +303,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]] }); @@ -316,7 +326,7 @@ describe('pubmatic analytics adapter', function () { 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&gdEn=1'); + 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'); @@ -326,8 +336,6 @@ describe('pubmatic analytics adapter', function () { 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.cns).to.equal('here-goes-gdpr-consent-string'); - expect(data.gdpr).to.equal(1); expect(data.tgid).to.equal(15); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -337,6 +345,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); @@ -361,6 +370,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); @@ -396,6 +406,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'); @@ -404,6 +415,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]] @@ -423,7 +435,7 @@ describe('pubmatic analytics adapter', function () { 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&gdEn=1'); + 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'); @@ -436,6 +448,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); @@ -490,7 +503,7 @@ describe('pubmatic analytics adapter', function () { 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&gdEn=1'); + 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'); @@ -503,6 +516,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); @@ -546,6 +560,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'); @@ -582,6 +597,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'); @@ -624,6 +640,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'); @@ -645,6 +662,7 @@ describe('pubmatic analytics adapter', function () { }); it('Logger: currency conversion check', function() { + setUANull(); setConfig({ adServerCurrency: 'JPY', rates: { @@ -672,13 +690,14 @@ describe('pubmatic analytics adapter', function () { 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&gdEn=1'); + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); 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].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); @@ -697,6 +716,329 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(100); expect(data.s[1].ps[0].ocry).to.equal('JPY'); + expect(data.dvc).to.deep.equal({'plt': 3}); + }); + + it('Logger: regexPattern in bid.params', function() { + setUAMobile(); + const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); + BID_REQUESTED_COPY.bids[1].params.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, BID_REQUESTED_COPY); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2); + 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.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].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + 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].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(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 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'); + expect(data.dvc).to.deep.equal({'plt': 2}); + // respective tracker slot + let firstTracker = requests[1].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.kgpv).to.equal('*'); + }); + + it('Logger: regexPattern in bid.bidResponse', function() { + const BID2_COPY = utils.deepClone(BID2); + BID2_COPY.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + 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, BID2_COPY); + 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, Object.assign({}, BID2_COPY, { + 'status': 'rendered' + })); + + 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.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].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + 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].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(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 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'); + expect(data.dvc).to.deep.equal({'plt': 1}); + // respective tracker slot + let firstTracker = requests[1].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.kgpv).to.equal('*'); + }); + + it('Logger: regexPattern in bid.params', function() { + const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); + BID_REQUESTED_COPY.bids[1].params.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, BID_REQUESTED_COPY); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2); + 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.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].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + 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].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(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 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'); + // respective tracker slot + let firstTracker = requests[1].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.kgpv).to.equal('*'); + }); + + it('Logger: regexPattern in bid.bidResponse', function() { + const BID2_COPY = utils.deepClone(BID2); + BID2_COPY.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + 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, BID2_COPY); + 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, Object.assign({}, BID2_COPY, { + 'status': 'rendered' + })); + + 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.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].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + 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].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(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 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'); + // respective tracker slot + let firstTracker = requests[1].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.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].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 abc440ac8ea..df3516579de 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -26,6 +26,9 @@ describe('PubMatic adapter', function () { let bannerBidResponse; let videoBidResponse; let schainConfig; + let outstreamBidRequest; + let validOutstreamBidRequest; + let outstreamVideoBidResponse; beforeEach(function () { schainConfig = { @@ -55,7 +58,7 @@ describe('PubMatic adapter', function () { } }, params: { - publisherId: '301', + publisherId: '5670', adSlot: '/15671365/DMDemo@300x250:0', kadfloor: '1.2', pmzoneid: 'aabc, ddef', @@ -656,7 +659,89 @@ describe('PubMatic adapter', function () { }] }] } - } + }; + outstreamBidRequest = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pubmatic', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + } + ]; + + validOutstreamBidRequest = { + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + auctionStart: 1585918458868, + bidderCode: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + bids: [{ + adUnitCode: 'video1', + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + bidId: '47acc48ad47af5', + bidRequestsCount: 1, + bidder: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }], + start: 11585918458869, + timeout: 3000 + }; + + outstreamVideoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '0fb4905b-1234-4152-86be-c6f6d259ba99', + 'impid': '47acc48ad47af5', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; }); describe('implementation', function () { @@ -706,29 +791,269 @@ 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 () { it('buildRequests function should not modify original bidRequests object', function () { let originalBidRequests = utils.deepClone(bidRequests); - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); 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); + let request = spec.buildRequests(nativeBidRequests, { + auctionId: 'new-auction-id' + }); expect(nativeBidRequests).to.deep.equal(originalBidRequests); }); it('Endpoint checking', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); expect(request.url).to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); expect(request.method).to.equal('POST'); - }); + }); - it('test flag not sent when pubmaticTest=true is absent in page url', function() { + it('should return bidderRequest property', function() { + let request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + expect(request.bidderRequest).to.equal(validOutstreamBidRequest); + }); + + it('bidderRequest should be undefined if bidderRequest is not present', function() { let request = spec.buildRequests(bidRequests); + expect(request.bidderRequest).to.be.undefined; + }); + + it('test flag not sent when pubmaticTest=true is absent in page url', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.test).to.equal(undefined); }); @@ -738,21 +1063,23 @@ describe('PubMatic adapter', function () { xit('test flag set to 1 when pubmaticTest=true is present in page url', function() { window.location.href += '#pubmaticTest=true'; // now all the test cases below will have window.location.href with #pubmaticTest=true - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.test).to.equal(1); }); it('Request params check', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.at).to.equal(1); // auction type expect(data.cur[0]).to.equal('USD'); // currency 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 @@ -772,10 +1099,30 @@ 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); }); + it('Set content from config, set site.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Merge the device info from config', function() { let sandbox = sinon.sandbox.create(); sandbox.stub(config, 'getConfig').callsFake((key) => { @@ -786,7 +1133,9 @@ describe('PubMatic adapter', function () { }; return config[key]; }); - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.device.js).to.equal(1); expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); @@ -808,7 +1157,9 @@ describe('PubMatic adapter', function () { }; return config[key]; }); - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.device.js).to.equal(1); expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); @@ -830,27 +1181,86 @@ describe('PubMatic adapter', function () { }; return config[key]; }); - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + 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.site).to.not.exist; + sandbox.restore(); + }); + + it('Set app, content from config, copy publisher and ext from site, unset site, config.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); 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(); + }); + + it('Set app.content, content from config, copy publisher and ext from site, unset site, config.app.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + const appContent = { + id: 'app-content-id-2' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org', + content: appContent + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + 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.content).to.deep.equal(appContent); expect(data.site).to.not.exist; sandbox.restore(); }); it('Request params check: without adSlot', function () { delete bidRequests[0].params.adSlot; - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.at).to.equal(1); // auction type expect(data.cur[0]).to.equal('USD'); // currency 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 @@ -869,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); }); @@ -900,7 +1311,9 @@ describe('PubMatic adapter', function () { } ]; /* case 1 - size passed in adslot */ - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].banner.w).to.equal(300); // width @@ -913,7 +1326,9 @@ describe('PubMatic adapter', function () { sizes: [[300, 600], [300, 250]] } }; - request = spec.buildRequests(bidRequests); + request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].banner.w).to.equal(300); // width @@ -927,7 +1342,9 @@ describe('PubMatic adapter', function () { sizes: [[300, 250], [300, 600]] } }; - request = spec.buildRequests(bidRequests); + request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].banner.w).to.equal(300); // width @@ -995,7 +1412,9 @@ describe('PubMatic adapter', function () { output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency */ - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); @@ -1007,7 +1426,9 @@ describe('PubMatic adapter', function () { */ delete multipleBidRequests[1].params.currency; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); @@ -1018,7 +1439,9 @@ describe('PubMatic adapter', function () { */ delete multipleBidRequests[0].params.currency; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].bidfloorcur).to.equal('USD'); expect(data.imp[1].bidfloorcur).to.equal('USD'); @@ -1029,12 +1452,46 @@ describe('PubMatic adapter', function () { */ multipleBidRequests[1].params.currency = 'AUD'; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].bidfloorcur).to.equal('USD'); expect(data.imp[1].bidfloorcur).to.equal('USD'); }); + it('Pass auctiondId as wiid if wiid is not passed in params', function () { + let bidRequest = { + auctionId: 'new-auction-id' + }; + delete bidRequests[0].params.wiid; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + 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.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 + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal('new-auction-id'); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + 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 + }); + it('Request params check with GDPR Consent', function () { let bidRequest = { gdprConsent: { @@ -1108,6 +1565,277 @@ 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; + + it('ortb2.site should be merged in the request', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); + sandbox.restore(); + }); + + it('ortb2.user should be merged in the request', function() { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'ortb2': { + user: { + yob: 1985 + } + } + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.yob).to.equal(1985); + sandbox.restore(); + }); + + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should not send if imp[].ext.data.pbadslot is empty string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); + }); + + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + expect(data.imp[0].ext.dfp_ad_unit_code).to.equal(adSlotValue); + }); + }); + + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); + }); + }); + describe('setting imp.floor using floorModule', function() { /* Use the minimum value among floor from floorModule per mediaType @@ -1143,7 +1871,9 @@ describe('PubMatic adapter', function () { it('bidfloor should be undefined if calculation is <= 0', function() { floorModuleTestData.banner.floor = 0; // lowest of them all newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(undefined); @@ -1152,7 +1882,9 @@ describe('PubMatic adapter', function () { it('ignore floormodule o/p if floor is not number', function() { floorModuleTestData.banner.floor = 'INR'; newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(2.5); // video will be lowest now @@ -1161,7 +1893,9 @@ describe('PubMatic adapter', function () { it('ignore floormodule o/p if currency is not matched', function() { floorModuleTestData.banner.currency = 'INR'; newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(2.5); // video will be lowest now @@ -1169,7 +1903,9 @@ describe('PubMatic adapter', function () { it('kadfloor is not passed, use minimum from floorModule', function() { newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(1.5); @@ -1177,7 +1913,9 @@ describe('PubMatic adapter', function () { it('kadfloor is passed as 3, use kadfloor as it is highest', function() { newRequest[0].params.kadfloor = '3.0';// yes, we want it as a string - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(3); @@ -1185,7 +1923,9 @@ describe('PubMatic adapter', function () { it('kadfloor is passed as 1, use min of fllorModule as it is highest', function() { newRequest[0].params.kadfloor = '1.0';// yes, we want it as a string - let request = spec.buildRequests(newRequest); + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.bidfloor).to.equal(1.5); @@ -1353,7 +2093,7 @@ describe('PubMatic adapter', function () { describe('ID5 Id', function() { it('send the id5 id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.id5id = 'id5-user-id'; + bidRequests[0].userId.id5id = { uid: 'id5-user-id' }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); @@ -1368,22 +2108,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.id5id = 1; + bidRequests[0].userId.id5id = { uid: 1 }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = []; + bidRequests[0].userId.id5id = { uid: [] }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = null; + bidRequests[0].userId.id5id = { uid: null }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = {}; + bidRequests[0].userId.id5id = { uid: {} }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); @@ -1443,7 +2183,7 @@ describe('PubMatic adapter', function () { 'source': 'liveramp.com', 'uids': [{ 'id': 'identity-link-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1484,7 +2224,7 @@ describe('PubMatic adapter', function () { 'source': 'liveintent.com', 'uids': [{ 'id': 'live-intent-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1517,7 +2257,7 @@ describe('PubMatic adapter', function () { describe('Parrable Id', function() { it('send the Parrable id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.parrableid = 'parrable-user-id'; + bidRequests[0].userId.parrableId = { eid: 'parrable-user-id' }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); @@ -1530,7 +2270,7 @@ describe('PubMatic adapter', function () { }]); }); - it('do not pass if not string', function() { + it('do not pass if not object with eid key', function() { bidRequests[0].userId = {}; bidRequests[0].userId.parrableid = 1; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); @@ -1566,7 +2306,7 @@ describe('PubMatic adapter', function () { 'source': 'britepool.com', 'uids': [{ 'id': 'britepool-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1636,14 +2376,77 @@ 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 () { - let request = spec.buildRequests(videoBidRequests); + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); 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]); @@ -1677,7 +2480,9 @@ describe('PubMatic adapter', function () { }); it('Request params check for 1 banner and 1 video ad', function () { - let request = spec.buildRequests(multipleMediaRequests); + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp).to.be.an('array') @@ -1711,7 +2516,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]); @@ -1745,7 +2549,9 @@ describe('PubMatic adapter', function () { }); it('Request params should have valid native bid request for all valid params', function () { - let request = spec.buildRequests(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].native).to.exist; expect(data.imp[0].native['request']).to.exist; @@ -1755,13 +2561,17 @@ describe('PubMatic adapter', function () { }); it('Request params should not have valid native bid request for non native request', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].native).to.not.exist; }); it('Request params should have valid native bid request with valid required param values for all valid params', function () { - let request = spec.buildRequests(nativeBidRequestsWithRequiredParam); + let request = spec.buildRequests(nativeBidRequestsWithRequiredParam, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].native).to.exist; expect(data.imp[0].native['request']).to.exist; @@ -1771,12 +2581,16 @@ describe('PubMatic adapter', function () { }); it('should not have valid native request if assets are not defined with minimum required params and only native is the slot', function () { - let request = spec.buildRequests(nativeBidRequestsWithoutAsset); + let request = spec.buildRequests(nativeBidRequestsWithoutAsset, { + auctionId: 'new-auction-id' + }); expect(request).to.deep.equal(undefined); }); it('Request params should have valid native bid request for all native params', function () { - let request = spec.buildRequests(nativeBidRequestsWithAllParams); + let request = spec.buildRequests(nativeBidRequestsWithAllParams, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.imp[0].native).to.exist; expect(data.imp[0].native['request']).to.exist; @@ -1786,7 +2600,9 @@ describe('PubMatic adapter', function () { }); it('Request params - should handle banner and video format in single adunit', function() { - let request = spec.buildRequests(bannerAndVideoBidRequests); + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.banner).to.exist; @@ -1797,7 +2613,9 @@ describe('PubMatic adapter', function () { // Case: when size is not present in adslo bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests); + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); data = data.imp[0]; expect(data.banner).to.exist; @@ -1819,7 +2637,9 @@ describe('PubMatic adapter', function () { */ bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - let request = spec.buildRequests(bannerAndVideoBidRequests); + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1838,7 +2658,9 @@ describe('PubMatic adapter', function () { bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests); + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); data = data.imp[0]; @@ -1856,7 +2678,9 @@ describe('PubMatic adapter', function () { */ bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; - request = spec.buildRequests(bannerAndVideoBidRequests); + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); data = data.imp[0]; @@ -1874,7 +2698,9 @@ describe('PubMatic adapter', function () { */ bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; - request = spec.buildRequests(bannerAndVideoBidRequests); + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); data = data.imp[0]; @@ -1886,14 +2712,18 @@ describe('PubMatic adapter', function () { delete bannerAndVideoBidRequests[0].mediaTypes.banner; bannerAndVideoBidRequests[0].params.sizes = [300, 250]; - let request = spec.buildRequests(bannerAndVideoBidRequests); + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.banner).to.not.exist; }); it('Request params - should handle banner and native format in single adunit', function() { - let request = spec.buildRequests(bannerAndNativeBidRequests); + let request = spec.buildRequests(bannerAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1908,7 +2738,9 @@ describe('PubMatic adapter', function () { }); it('Request params - should handle video and native format in single adunit', function() { - let request = spec.buildRequests(videoAndNativeBidRequests); + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1921,7 +2753,9 @@ describe('PubMatic adapter', function () { }); it('Request params - should handle banner, video and native format in single adunit', function() { - let request = spec.buildRequests(bannerVideoAndNativeBidRequests); + let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1943,7 +2777,9 @@ describe('PubMatic adapter', function () { delete bannerAndNativeBidRequests[0].mediaTypes.banner; bannerAndNativeBidRequests[0].sizes = [729, 90]; - let request = spec.buildRequests(bannerAndNativeBidRequests); + let request = spec.buildRequests(bannerAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1966,7 +2802,9 @@ describe('PubMatic adapter', function () { sponsoredBy: { required: true }, clickUrl: { required: true } } - let request = spec.buildRequests(bannerAndNativeBidRequests); + let request = spec.buildRequests(bannerAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; @@ -1987,13 +2825,102 @@ describe('PubMatic adapter', function () { sponsoredBy: { required: true }, clickUrl: { required: true } } - let request = spec.buildRequests(videoAndNativeBidRequests); + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data = data.imp[0]; 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 () { @@ -2003,17 +2930,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', @@ -2050,33 +2966,41 @@ describe('PubMatic adapter', function () { } ]; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); /* case 1 - 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); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); 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 */ multipleBidRequests[0].params.dctr = 123; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); 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 () { @@ -2133,7 +3057,9 @@ describe('PubMatic adapter', function () { } ]; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); // case 1 - deals are passed as expected, ['', ''] , in both adUnits expect(data.imp[0].pmp).to.deep.equal({ @@ -2161,19 +3087,25 @@ describe('PubMatic adapter', function () { // case 2 - deals not present in adunit[0] delete multipleBidRequests[0].params.deals; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].pmp).to.not.exist; // case 3 - deals is present in adunit[0], but is not an array multipleBidRequests[0].params.deals = 123; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].pmp).to.not.exist; // case 4 - deals is present in adunit[0] as an array but one of the value is not a string multipleBidRequests[0].params.deals = [123, 'deal-id-1']; - request = spec.buildRequests(multipleBidRequests); + request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); data = JSON.parse(request.data); expect(data.imp[0].pmp).to.deep.equal({ 'private_auction': 0, @@ -2241,21 +3173,27 @@ describe('PubMatic adapter', function () { it('bcat: pass only strings', function() { multipleBidRequests[0].params.bcat = [1, 2, 3, 'IAB1', 'IAB2']; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); }); it('bcat: pass strings with length greater than 3', function() { multipleBidRequests[0].params.bcat = ['AB', 'CD', 'IAB1', 'IAB2']; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); }); it('bcat: trim the strings', function() { multipleBidRequests[0].params.bcat = [' IAB1 ', ' IAB2 ']; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); }); @@ -2264,7 +3202,9 @@ describe('PubMatic adapter', function () { // multi slot multipleBidRequests[0].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; multipleBidRequests[1].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB3']; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2', 'IAB3']); }); @@ -2273,7 +3213,9 @@ describe('PubMatic adapter', function () { // multi slot multipleBidRequests[0].params.bcat = ['', 'IAB', 'IAB']; multipleBidRequests[1].params.bcat = [' ', 22, 99999, 'IA']; - let request = spec.buildRequests(multipleBidRequests); + let request = spec.buildRequests(multipleBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); expect(data.bcat).to.deep.equal(undefined); }); @@ -2281,7 +3223,9 @@ describe('PubMatic adapter', function () { describe('Response checking', function () { it('should check for valid response values', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); let response = spec.interpretResponse(bidResponses, request); expect(response).to.be.an('array').with.length.above(0); @@ -2296,12 +3240,13 @@ describe('PubMatic adapter', function () { } expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); + expect(response[0].netRevenue).to.equal(true); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); expect(response[0].meta.buyerId).to.equal(976); expect(response[0].meta.clickUrl).to.equal('blackrock.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); expect(response[0].pm_seat).to.equal(bidResponses.body.seatbid[0].seat); @@ -2319,12 +3264,13 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); expect(response[1].meta.buyerId).to.equal(832); expect(response[1].meta.clickUrl).to.equal('hivehome.com'); + expect(response[1].meta.advertiserDomains[0]).to.equal('hivehome.com'); expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); expect(response[1].pm_seat).to.equal(bidResponses.body.seatbid[1].seat || null); @@ -2333,7 +3279,9 @@ describe('PubMatic adapter', function () { }); it('should check for dealChannel value selection', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let response = spec.interpretResponse(bidResponses, request); expect(response).to.be.an('array').with.length.above(0); expect(response[0].dealChannel).to.equal('PMPG'); @@ -2341,7 +3289,9 @@ describe('PubMatic adapter', function () { }); it('should check for unexpected dealChannel value selection', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let updateBiResponse = bidResponses; updateBiResponse.body.seatbid[0].bid[0].ext.deal_channel = 11; @@ -2352,7 +3302,9 @@ describe('PubMatic adapter', function () { }); it('should have a valid native bid response', function() { - let request = spec.buildRequests(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests, { + auctionId: 'new-auction-id' + }); let data = JSON.parse(request.data); data.imp[0].id = '2a5571261281d4'; request.data = JSON.stringify(data); @@ -2370,29 +3322,221 @@ describe('PubMatic adapter', function () { }); it('should check for valid banner mediaType in case of multiformat request', function() { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); let response = spec.interpretResponse(bannerBidResponse, request); expect(response[0].mediaType).to.equal('banner'); }); it('should check for valid video mediaType in case of multiformat request', function() { - let request = spec.buildRequests(videoBidRequests); + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); }); it('should check for valid native mediaType in case of multiformat request', function() { - let request = spec.buildRequests(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests, { + auctionId: 'new-auction-id' + }); let response = spec.interpretResponse(nativeBidResponse, request); expect(response[0].mediaType).to.equal('native'); }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is native', function() { + let request = spec.buildRequests(nativeBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(nativeBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is of banner', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + 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 () { diff --git a/test/spec/modules/pubperfAnalyticsAdapter_spec.js b/test/spec/modules/pubperfAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b316b44617a --- /dev/null +++ b/test/spec/modules/pubperfAnalyticsAdapter_spec.js @@ -0,0 +1,55 @@ +import pubperfAnalytics from 'modules/pubperfAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let utils = require('src/utils.js'); +let constants = require('src/constants.json'); + +describe('Pubperf Analytics Adapter', function() { + describe('Prebid Manager Analytic tests', function() { + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'logError'); + }); + + afterEach(function() { + events.getEvents.restore(); + utils.logError.restore(); + }); + + it('should throw error, when pubperf_pbjs is not defined', function() { + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + expect(server.requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + + it('track event without errors', function() { + sinon.spy(pubperfAnalytics, 'track'); + + window['pubperf_pbjs'] = function() {}; + + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + sinon.assert.callCount(pubperfAnalytics.track, 6); + }); + }); +}); diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index 5e4b2be894e..3be4ea3d69c 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -1,47 +1,164 @@ +import { expect } from 'chai'; import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter.js'; +import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { - after(function () { + let requests; + let sandbox; + let xhr; + let clock; + let mock = {}; + + mock.DEFAULT_PW_CONFIG = { + provider: 'pubwiseanalytics', + options: { + site: ['b1ccf317-a6fc-428d-ba69-0c9c208aa61c'], + custom: {'c_script_type': 'test-script-type', 'c_host': 'test-host', 'c_slot1': 'test-slot1', 'c_slot2': 'test-slot2', 'c_slot3': 'test-slot3', 'c_slot4': 'test-slot4'} + } + }; + mock.AUCTION_INIT = {auctionId: '53c35d77-bd62-41e7-b920-244140e30c77'}; + mock.AUCTION_INIT_EXTRAS = { + auctionId: '53c35d77-bd62-41e7-b920-244140e30c77', + adUnitCodes: 'not empty', + adUnits: '', + bidderRequests: ['0'], + bidsReceived: '0', + config: {test: 'config'}, + noBids: 'no bids today', + winningBids: 'winning bids', + extraProp: 'extraProp retained' + }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + sandbox.stub(events, 'getEvents').returns([]); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); pubwiseAnalytics.disableAnalytics(); }); describe('enableAnalytics', function () { beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - events.getEvents.restore(); + requests = []; }); it('should catch all events', function () { - sinon.spy(pubwiseAnalytics, 'track'); + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); - adapterManager.registerAnalyticsAdapter({ - code: 'pubwiseanalytics', - adapter: pubwiseAnalytics - }); + sandbox.spy(pubwiseAnalytics, 'track'); - adapterManager.enableAnalytics({ - provider: 'pubwiseanalytics', - options: { - site: ['test-test-test-test'] - } - }); - - events.emit(constants.EVENTS.AUCTION_INIT, {}); + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); events.emit(constants.EVENTS.BID_REQUESTED, {}); events.emit(constants.EVENTS.BID_RESPONSE, {}); events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AD_RENDER_FAILED, {}); + events.emit(constants.EVENTS.TCF2_ENFORCEMENT, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + // forces flush events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + // eslint-disable-next-line + //console.log(requests); /* testing for 6 calls, including the 2 we're not currently tracking */ - sinon.assert.callCount(pubwiseAnalytics.track, 6); + sandbox.assert.callCount(pubwiseAnalytics.track, 7); + }); + + it('should initialize the auction properly', function () { + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); + + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + // force flush + clock.tick(500); + + /* check for critical values */ + let request = requests[0]; + let data = JSON.parse(request.requestBody); + // eslint-disable-next-line + // console.log(data.metaData); + expect(data.metaData, 'metaData property').to.exist; + expect(data.metaData.pbjs_version, 'pbjs version').to.equal('$prebid.version$') + expect(data.metaData.session_id, 'session id').not.to.be.empty + expect(data.metaData.activation_id, 'activation id').not.to.be.empty + + // check custom metadata slots + expect(data.metaData.c_script_type, 'c_script_type property').to.exist; + expect(data.metaData.c_script_type, 'c_script_type').not.to.be.empty + expect(data.metaData.c_script_type).to.equal('test-script-type'); + + expect(data.metaData.c_host, 'c_host property').to.exist; + expect(data.metaData.c_host, 'c_host').not.to.be.empty + expect(data.metaData.c_host).to.equal('test-host'); + + expect(data.metaData.c_slot1, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot1, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot1).to.equal('test-slot1'); + + expect(data.metaData.c_slot2, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot2, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot2).to.equal('test-slot2'); + + expect(data.metaData.c_slot3, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot3, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot3).to.equal('test-slot3'); + + expect(data.metaData.c_slot4, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot4, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot4).to.equal('test-slot4'); + + // check for version info too + expect(data.metaData.pw_version, 'pw_version property').to.exist; + expect(data.metaData.pbjs_version, 'pbjs_version property').to.exist; + }); + + it('should remove extra data on init', function () { + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); + + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT_EXTRAS); + // force flush + clock.tick(500); + + /* check for critical values */ + let request = requests[0]; + let data = JSON.parse(request.requestBody); + + // check the basics + expect(data.eventList, 'eventList property').to.exist; + expect(data.eventList[0], 'eventList property').to.exist; + expect(data.eventList[0].args, 'eventList property').to.exist; + + // eslint-disable-next-line + // console.log(data.eventList[0].args); + + let eventArgs = data.eventList[0].args; + // the props we want removed should go away + expect(eventArgs.adUnitCodes, 'adUnitCodes property').not.to.exist; + expect(eventArgs.bidderRequests, 'adUnitCodes property').not.to.exist; + expect(eventArgs.bidsReceived, 'adUnitCodes property').not.to.exist; + expect(eventArgs.config, 'adUnitCodes property').not.to.exist; + expect(eventArgs.noBids, 'adUnitCodes property').not.to.exist; + expect(eventArgs.winningBids, 'adUnitCodes property').not.to.exist; + + // the extra prop should still exist + expect(eventArgs.extraProp, 'adUnitCodes property').to.exist; }); }); }); diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js new file mode 100644 index 00000000000..450b028f6c7 --- /dev/null +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -0,0 +1,575 @@ +// import or require modules necessary for the test, e.g.: + +import {expect} from 'chai'; +import {spec} from 'modules/pubwiseBidAdapter.js'; +import {_checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import {_parseAdSlot} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import * as utils from 'src/utils.js'; + +const sampleRequestBanner = { + 'id': '6c148795eb836a', + 'tagid': 'div-gpt-ad-1460505748561-0', + 'bidfloor': 1, + 'secure': 1, + 'bidfloorcur': 'USD', + 'banner': { + 'w': 300, + 'h': 250, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ], + 'pos': 0, + 'topframe': 1 + } +}; + +const sampleRequest = { + 'at': 1, + 'cur': [ + 'USD' + ], + 'imp': [ + sampleRequestBanner, + { + 'id': '7329ddc1d84eb3', + 'tagid': 'div-gpt-ad-1460505748561-1', + 'secure': 1, + 'bidfloorcur': 'USD', + 'native': { + 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":5,"required":1,"data":{"type":2}},{"id":2,"required":1,"img":{"type":{"ID":2,"KEY":"image","TYPE":0},"w":150,"h":50}},{"id":4,"required":1,"data":{"type":1}}]}' + } + } + ], + 'site': { + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'ref': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'publisher': { + 'id': 'xxxxxx' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/86.0.4240.198 Safari/537.36', + 'js': 1, + 'dnt': 0, + 'h': 600, + 'w': 800, + 'language': 'en-US', + 'geo': { + 'lat': 33.91989876432274, + 'lon': -84.38897708175764 + } + }, + 'user': { + 'gender': 'M', + 'geo': { + 'lat': 33.91989876432274, + 'lon': -84.38897708175764 + }, + 'yob': 2000 + }, + 'test': 0, + 'ext': { + 'version': '0.0.1' + }, + 'source': { + 'tid': '2c8cd034-f068-4419-8c30-f07292c0d17b' + } +}; + +const sampleValidBannerBidRequest = { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx', + 'bidFloor': '1.00', + 'currency': 'USD', + 'gender': 'M', + 'lat': '33.91989876432274', + 'lon': '-84.38897708175764', + 'yob': '2000', + 'bcat': ['IAB25-3', 'IAB26-1', 'IAB26-2', 'IAB26-3', 'IAB26-4'], + }, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '6c148795eb836a', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 +}; + +const sampleValidBidRequests = [ + sampleValidBannerBidRequest, + { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx' + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'nativeParams': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '2c8cd034-f068-4419-8c30-f07292c0d17b', + 'sizes': [], + 'bidId': '30ab7516a51a7c', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +] + +const sampleBidderBannerRequest = { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx', + 'height': 250, + 'width': 300, + 'gender': 'M', + 'yob': '2000', + 'lat': '33.91989876432274', + 'lon': '-84.38897708175764', + 'bidFloor': '1.00', + 'currency': 'USD', + 'adSlot': '', + 'adUnit': '', + 'bcat': [ + 'IAB25-3', + 'IAB26-1', + 'IAB26-2', + 'IAB26-3', + 'IAB26-4', + ], + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '6c148795eb836a', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, +}; + +const sampleBidderRequest = { + 'bidderCode': 'pubwise', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'bidderRequestId': '18a45bff5ff705', + 'bids': [ + sampleBidderBannerRequest, + { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx' + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'nativeParams': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '2c8cd034-f068-4419-8c30-f07292c0d17b', + 'sizes': [], + 'bidId': '30ab7516a51a7c', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1606269202001, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, + 'refererInfo': { + 'referer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true' + ], + 'canonicalUrl': null + }, + 'start': 1606269202004 +}; + +const sampleRTBResponse = { + 'body': { + 'id': '1606251348404', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1606579704052', + 'impid': '6c148795eb836a', + 'price': 1.23, + 'adm': '\u003cdiv style="box-sizing: border-box;width:298px;height:248px;border: 1px solid rgba(0,0,0,.25);border-radius:10px;"\u003e\n\t\u003ch3 style="margin-top:80px;text-align: center;"\u003ePubWise Test Bid\u003c/h3\u003e\n\u003c/div\u003e', + 'crid': 'test', + 'w': 300, + 'h': 250 + }, + { + 'id': '1606579704052', + 'impid': '7329ddc1d84eb3', + 'price': 1.23, + 'adm': '{"ver":"1.2","assets":[{"id":1,"title":{"text":"PubWise Test"}},{"id":2,"img":{"type":3,"url":"http://www.pubwise.io","w":300,"h":250}},{"id":3,"img":{"type":1,"url":"http://www.pubwise.io","w":150,"h":125}},{"id":5,"data":{"type":2,"value":"PubWise Test Desc"}},{"id":4,"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":"http://www.pubwise.io"}}', + 'crid': 'test', + 'w': 300, + 'h': 250 + } + ] + } + ], + 'bidid': 'testtesttest' + } +}; + +const samplePBBidObjects = [ + { + 'requestId': '6c148795eb836a', + 'cpm': '1.23', + 'width': 300, + 'height': 250, + 'creativeId': 'test', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '
\n\t

PubWise Test Bid

\n
', + 'pw_seat': null, + 'pw_dspid': null, + 'partnerImpId': '1606579704052', + 'meta': {}, + 'mediaType': 'banner', + }, + { + 'requestId': '7329ddc1d84eb3', + 'cpm': '1.23', + 'width': 300, + 'height': 250, + 'creativeId': 'test', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"text\":\"PubWise Test\"}},{\"id\":2,\"img\":{\"type\":3,\"url\":\"http://www.pubwise.io\",\"w\":300,\"h\":250}},{\"id\":3,\"img\":{\"type\":1,\"url\":\"http://www.pubwise.io\",\"w\":150,\"h\":125}},{\"id\":5,\"data\":{\"type\":2,\"value\":\"PubWise Test Desc\"}},{\"id\":4,\"data\":{\"type\":1,\"value\":\"PubWise.io\"}}],\"link\":{\"url\":\"http://www.pubwise.io\"}}', + 'pw_seat': null, + 'pw_dspid': null, + 'partnerImpId': '1606579704052', + 'mediaType': 'native', + 'native': { + 'body': 'PubWise Test Desc', + 'icon': { + 'height': 125, + 'url': 'http://www.pubwise.io', + 'width': 150, + }, + 'image': { + 'height': 250, + 'url': 'http://www.pubwise.io', + 'width': 300, + }, + 'sponsoredBy': 'PubWise.io', + 'title': 'PubWise Test' + }, + 'meta': {}, + 'impressionTrackers': [], + 'jstracker': [], + 'clickTrackers': [], + 'clickUrl': 'http://www.pubwise.io' + } +]; + +describe('PubWiseAdapter', function () { + describe('Properly Validates Bids', function () { + it('valid bid', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 'xxxxxx' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('valid bid: extra fields are ok', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 'xxxxxx', + gender: 'M', + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid: no siteId', function () { + let inValidBid = { + bidder: 'pubwise', + params: { + gender: 'M', + } + }, + isValid = spec.isBidRequestValid(inValidBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid: siteId should be a string', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 123456 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + + describe('Handling Request Construction', function () { + it('bid requests are not mutable', function() { + let sourceBidRequest = utils.deepClone(sampleValidBidRequests) + spec.buildRequests(sampleValidBidRequests, {auctinId: 'placeholder'}); + expect(sampleValidBidRequests).to.deep.equal(sourceBidRequest, 'Should be unedited as they are used elsewhere'); + }); + it('should handle complex bidRequest', function() { + let request = spec.buildRequests(sampleValidBidRequests, sampleBidderRequest); + expect(request.bidderRequest).to.equal(sampleBidderRequest); + }); + it('must conform to API for buildRequests', function() { + let request = spec.buildRequests(sampleValidBidRequests); + expect(request.bidderRequest).to.be.undefined; + }); + }); + + describe('Identifies Media Types', function () { + it('identifies native adm type', function() { + let adm = '{"ver":"1.2","assets":[{"title":{"text":"PubWise Test"}},{"img":{"type":3,"url":"http://www.pubwise.io"}},{"img":{"type":1,"url":"http://www.pubwise.io"}},{"data":{"type":2,"value":"PubWise Test Desc"}},{"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":""}}'; + let newBid = {mediaType: 'unknown'}; + _checkMediaType(adm, newBid); + expect(newBid.mediaType).to.equal('native', adm + ' Is a Native adm'); + }); + + it('identifies banner adm type', function() { + let adm = '

PubWise Test Bid

'; + let newBid = {mediaType: 'unknown'}; + _checkMediaType(adm, newBid); + expect(newBid.mediaType).to.equal('banner', adm + ' Is a Banner adm'); + }); + }); + + describe('Properly Parses AdSlot Data', function () { + it('parses banner', function() { + let testBid = utils.deepClone(sampleValidBannerBidRequest) + _parseAdSlot(testBid) + expect(testBid).to.deep.equal(sampleBidderBannerRequest); + }); + }); + + describe('Properly Handles Response', function () { + it('handles response with muiltiple responses', function() { + // the request when it comes back is on the data object + let pbResponse = spec.interpretResponse(sampleRTBResponse, {'data': sampleRequest}) + expect(pbResponse).to.deep.equal(samplePBBidObjects); + }); + }); +}); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js new file mode 100644 index 00000000000..06bb5b5f638 --- /dev/null +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -0,0 +1,199 @@ +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); + const ENDPOINT = 'https://api.primecaster.net/adlogue/api/slot/bid'; + + 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: 'pubx', + params: { + sid: '12345abc' + } + }; + + 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 = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + id: '26c1ee0038ac11', + params: { + sid: '12345abc' + } + } + ]; + + const data = { + banner: { + sid: '12345abc' + } + }; + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should attach params to the banner request', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.deep.equal(data.banner); + }); + }); + + describe('getUserSyncs', function () { + const sandbox = sinon.sandbox.create(); + + const keywordsText = 'meta1,meta2,meta3,meta4,meta5'; + const descriptionText = 'description1description2description3description4description5description'; + + let documentStubMeta; + + beforeEach(function () { + documentStubMeta = sandbox.stub(document, 'getElementsByName'); + const metaElKeywords = document.createElement('meta'); + metaElKeywords.setAttribute('name', 'keywords'); + metaElKeywords.setAttribute('content', keywordsText); + documentStubMeta.withArgs('keywords').returns([metaElKeywords]); + + const metaElDescription = document.createElement('meta'); + metaElDescription.setAttribute('name', 'description'); + metaElDescription.setAttribute('content', descriptionText); + documentStubMeta.withArgs('description').returns([metaElDescription]); + }); + + afterEach(function () { + documentStubMeta.restore(); + }); + + let kwString = ''; + let kwEnc = ''; + let descContent = ''; + let descEnc = ''; + + it('returns empty sync array when iframe is not enabled', function () { + const syncOptions = {}; + expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); + }); + + it('returns kwEnc when there is kwTag with more than 20 length', function () { + const kwArray = keywordsText.substr(0, 20).split(','); + kwArray.pop(); + kwString = kwArray.join(); + kwEnc = encodeURIComponent(kwString); + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pkw=${kwEnc}`); + }); + + it('returns kwEnc when there is kwTag with more than 60 length', function () { + descContent = descContent.substr(0, 60); + descEnc = encodeURIComponent(descContent); + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pkw=${descEnc}`); + }); + + it('returns titleEnc when there is titleContent with more than 30 length', function () { + let titleText = 'title1title2title3title4title5title'; + const documentStubTitle = sandbox.stub(document, 'title').value(titleText); + + if (titleText.length > 30) { + titleText = titleText.substr(0, 30); + } + + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pt=${encodeURIComponent(titleText)}`); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + TTL: 300, + adm: '
some creative
', + cid: 'TKmB', + cpm: 500, + currency: 'JPY', + height: 250, + width: 300, + adomains: [ + 'test.com' + ], + } + } + + const bidRequests = [ + { + id: '26c1ee0038ac11', + params: { + sid: '12345abc' + } + } + ]; + + const bidResponses = [ + { + requestId: '26c1ee0038ac11', + cpm: 500, + currency: 'JPY', + width: 300, + height: 250, + creativeId: 'TKmB', + netRevenue: true, + ttl: 300, + ad: '
some creative
', + meta: { + advertiserDomains: [ + 'test.com' + ] + }, + } + ]; + it('should return empty array when required param is empty', function () { + const serverResponseWithCidEmpty = { + body: { + TTL: 300, + adm: '
some creative
', + cid: '', + cpm: '', + currency: 'JPY', + height: 250, + width: 300, + } + } + const result = spec.interpretResponse(serverResponseWithCidEmpty, bidRequests[0]); + expect(result).to.be.empty; + }); + it('handles banner responses', function () { + const result = spec.interpretResponse(serverResponse, bidRequests[0])[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.currency).to.equal(bidResponses[0].currency); + 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 new file mode 100644 index 00000000000..3d9be082be3 --- /dev/null +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -0,0 +1,685 @@ +import pubxaiAnalyticsAdapter from 'modules/pubxaiAnalyticsAdapter.js'; +import { getDeviceType, getBrowser, getOS } from 'modules/pubxaiAnalyticsAdapter.js'; +import { + expect +} from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { + server +} from 'test/mocks/xhr.js'; + +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('pubxai analytics adapter', function() { + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function() { + events.getEvents.restore(); + }); + + describe('track', function() { + let initOptions = { + samplingRate: '1', + pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' + }; + + let location = utils.getWindowLocation(); + + let prebidEvent = { + 'auctionInit': { + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'timestamp': 1603865707180, + 'auctionStatus': 'inProgress', + 'adUnits': [{ + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'test model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloorProvider', + 'fetchStatus': 'success' + } + }], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' + }], + 'adUnitCodes': [ + '/19968336/header-bid-tag-1' + ], + 'bidderRequests': [{ + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'test model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloorProvider', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000, + 'config': { + 'samplingRate': '1', + 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' + } + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'test model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloorProvider', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }, + 'bidTimeout': [], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + }, + 'auctionEnd': { + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'timestamp': 1616654312804, + 'auctionEnd': 1616654313090, + 'auctionStatus': 'completed', + 'adUnits': [{ + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'test model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloorProvider', + 'fetchStatus': 'success' + } + }], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' + }], + 'adUnitCodes': [ + '/19968336/header-bid-tag-1' + ], + 'bidderRequests': [{ + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'test model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloorProvider', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }], + 'noBids': [], + 'bidsReceived': [{ + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [{ + 'placementId': 13144370 + }] + }], + 'winningBids': [], + 'timeout': 1000 + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [{ + 'placementId': 13144370 + }] + }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, + }; + + let expectedAfterBid = { + 'bids': [{ + '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, + 'requestTimestamp': 1616654312804, + 'creativeId': 96846035, + 'currency': 'USD', + 'cpm': 0.5, + 'netRevenue': true, + 'mediaType': 'banner', + 'statusMessage': 'Bid available', + 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'timeToRespond': 267, + 'responseTimestamp': 1616654313071 + }], + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search, + 'adUnitCount': 1 + }, + 'floorDetail': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false + }, + 'deviceDetail': { + 'platform': navigator.platform, + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() + }, + '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', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'floorProvider': 'PubXFloorProvider', + 'isWinningBid': true, + 'mediaType': 'banner', + 'netRevenue': true, + 'placementId': 13144370, + 'renderedSize': '300x250', + 'renderStatus': 4, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'status': 'rendered', + 'statusMessage': 'Bid available', + 'timeToRespond': 267 + }, + 'deviceDetail': { + 'platform': navigator.platform, + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() + }, + 'initOptions': initOptions + } + + adapterManager.registerAnalyticsAdapter({ + code: 'pubxai', + adapter: pubxaiAnalyticsAdapter + }); + + beforeEach(function() { + adapterManager.enableAnalytics({ + provider: 'pubxai', + options: initOptions + }); + }); + + afterEach(function() { + pubxaiAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', function() { + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + expect(server.requests.length).to.equal(1); + + let realAfterBid = JSON.parse(server.requests[0].requestBody); + + expect(realAfterBid).to.deep.equal(expectedAfterBid); + + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + + expect(server.requests.length).to.equal(2); + + let winEventData = JSON.parse(server.requests[1].requestBody); + + expect(winEventData).to.deep.equal(expectedAfterBidWon); + }); + }); +}); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 4b21856b68e..92f7aa0b70d 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -19,6 +19,11 @@ describe('PulsePoint Adapter Tests', function () { } }, { placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, bidId: 'bid23456', params: { cp: 'p10000', @@ -72,6 +77,11 @@ describe('PulsePoint Adapter Tests', function () { }]; const additionalParamsConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -89,6 +99,11 @@ describe('PulsePoint Adapter Tests', function () { const ortbParamsSlotConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -146,6 +161,11 @@ describe('PulsePoint Adapter Tests', function () { const schainParamsSlotConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -234,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 = { @@ -244,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, @@ -262,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 () { @@ -612,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'); @@ -630,10 +655,20 @@ describe('PulsePoint Adapter Tests', function () { britepoolid: 'britepool_id123', criteoId: 'criteo_id234', idl_env: 'idl_id123', - id5id: 'id5id_234', - parrableid: 'parrable_id234', + id5id: { uid: 'id5id_234' }, + 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) { @@ -652,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); @@ -681,7 +722,10 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.be.null; expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); - expect(ortbRequest.imp[1].banner.format).to.be.null; + expect(ortbRequest.imp[1].banner.format).to.not.be.null; + expect(ortbRequest.imp[1].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[1].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[1].banner.format[0].h).to.equal(90); // adsize on response const ortbResponse = { seatbid: [{ @@ -701,4 +745,113 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.width).to.equal(728); expect(bid.height).to.equal(90); }); + it('Verify multi-format response', function () { + const bidRequests = deepClone(slotConfigs); + bidRequests[0].mediaTypes['native'] = { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + }; + bidRequests[1].params.video = { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.not.null; + expect(request.data).to.be.not.null; + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(2); + // adsize on response + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + crid: 'Creative#123', + w: 728, + h: 90 + }, { + impid: ortbRequest.imp[1].id, + price: 2.5, + adm: '', + crid: 'Creative#234', + w: 728, + h: 90 + }] + }] + }; + // request has both types - banner and native, response is parsed as banner. + // for impression#2, response is parsed as video + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(2); + const bid = bids[0]; + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + 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 cd168ec61e6..5e0d129581c 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -7,7 +7,8 @@ import { QUANTCAST_TEST_PUBLISHER, QUANTCAST_PROTOCOL, QUANTCAST_PORT, - spec as qcSpec + spec as qcSpec, + storage } from '../../../modules/quantcastBidAdapter.js'; import { newBidder } from '../../../src/adapters/bidderFactory.js'; import { parseUrl } from 'src/utils.js'; @@ -42,18 +43,19 @@ describe('Quantcast adapter', function () { canonicalUrl: 'http://example.com/hello.html' } }; + + 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 } } }; @@ -140,7 +142,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; it('sends banner bid requests contains all the required parameters', function () { @@ -171,6 +174,9 @@ 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); @@ -208,7 +214,68 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + 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); + 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)); @@ -217,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']; @@ -244,14 +314,18 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); }); 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 = { @@ -276,7 +350,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -340,7 +415,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); @@ -430,26 +506,6 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows TCF v2 request from Germany for purpose 1', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - publisherCC: 'DE', - purposeOneTreatment: true - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - - expect(parsed.gdprSignal).to.equal(1); - expect(parsed.gdprConsent).to.equal('consentString'); - }); - it('allows TCF v2 request when Quantcast has consent for purpose 1', function() { const bidderRequest = { gdprConsent: { @@ -604,6 +660,13 @@ describe('Quantcast adapter', function () { expect(parsed.uspConsent).to.equal('consentString'); }); + it('propagates Quantcast first-party cookie (fpa)', function() { + storage.setCookie('__qca', 'P0-TestFPA'); + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + expect(parsed.fpa).to.equal('P0-TestFPA'); + }); + describe('propagates coppa', function() { let sandbox; beforeEach(() => { @@ -663,7 +726,10 @@ describe('Quantcast adapter', function () { '
Quantcast
', creativeId: 1001, width: 300, - height: 250 + height: 250, + meta: { + advertiserDomains: ['dailymail.com'] + } } ] }; @@ -723,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); @@ -743,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 new file mode 100644 index 00000000000..e9d44dd6124 --- /dev/null +++ b/test/spec/modules/quantcastIdSystem_spec.js @@ -0,0 +1,383 @@ +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() { + sinon.stub(coppaDataHandler, 'getCoppa'); + sinon.stub(utils, 'triggerPixel'); + sinon.stub(window, 'addEventListener'); + }); + + 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 () { + const id = quantcastIdSubmodule.getId(); + 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/quantumBidAdapter_spec.js b/test/spec/modules/quantumBidAdapter_spec.js deleted file mode 100644 index c03d74ea52e..00000000000 --- a/test/spec/modules/quantumBidAdapter_spec.js +++ /dev/null @@ -1,325 +0,0 @@ -import { expect } from 'chai' -import { spec } from 'modules/quantumBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' - -const ENDPOINT = 'https://s.sspqns.com/hb' -const REQUEST = { - 'bidder': 'quantum', - 'sizes': [[300, 250]], - 'renderMode': 'banner', - 'params': { - placementId: 21546 - } -} - -const NATIVE_REQUEST = { - 'bidder': 'quantum', - 'mediaType': 'native', - 'sizes': [[0, 0]], - 'params': { - placementId: 21546 - } -} - -const serverResponse = { - 'price': 0.3, - 'debug': [ - '' - ], - 'is_fallback': false, - 'nurl': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///', - 'clicktrackers': ['https://elasticad.net'] - }, - 'assets': [ - { - 'id': 1, - 'title': { - 'text': 'ad.SSP.1x1' - }, - 'required': 1 - }, - { - 'id': 2, - 'img': { - 'w': 15, - 'h': 15, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' - } - }, - { - 'id': 3, - 'data': { - 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' - }, - 'required': 1 - }, - { - 'id': 4, - 'img': { - 'w': 500, - 'h': 500, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' - } - }, - { - 'id': 6, - 'video': { - 'vasttag': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2001, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2002, - 'data': { - 'value': 'vast' - } - }, - { - 'id': 2007, - 'data': { - 'value': 'click' - } - }, - { - 'id': 10, - 'data': { - 'value': 'ad.SSP.1x1 sponsor' - } - }, - { - 'id': 2003, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2004, - 'data': { - 'value': 'prism' - } - }, - { - 'id': 2005, - 'data': { - 'value': '/home' - } - }, - { - 'id': 2006, - 'data': { - 'value': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2022, - 'data': { - 'value': 'Lorem ipsum....' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -const nativeServerResponse = { - 'price': 0.3, - 'debug': [ - '' - ], - 'is_fallback': false, - 'nurl': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///' - }, - 'assets': [ - { - 'id': 1, - 'title': { - 'text': 'ad.SSP.1x1' - }, - 'required': 1 - }, - { - 'id': 2, - 'img': { - 'w': 15, - 'h': 15, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' - } - }, - { - 'id': 3, - 'data': { - 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' - }, - 'required': 1 - }, - { - 'id': 4, - 'img': { - 'w': 500, - 'h': 500, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' - } - }, - { - 'id': 2007, - 'data': { - 'value': 'click' - } - }, - { - 'id': 10, - 'data': { - 'value': 'ad.SSP.1x1 sponsor' - } - }, - - { - 'id': 2003, - 'data': { - 'value': 'https://elasticad.net' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -describe('quantumBidAdapter', 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 true when required params found', function () { - expect(spec.isBidRequestValid(REQUEST)).to.equal(true) - }) - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, REQUEST) - delete bid.params - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - let bidRequests = [REQUEST] - - const request = spec.buildRequests(bidRequests, {}) - - it('sends bid request to ENDPOINT via GET', function () { - expect(request[0].method).to.equal('GET') - }) - }) - - describe('GDPR conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: { - consentString: 'awefasdfwefasdfasd', - gdprApplies: true - } - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.equal(1); - expect(requests[0].data.quantx_user_consent_string).to.equal('awefasdfwefasdfasd'); - }); - }); - - describe('GDPR absence conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: undefined - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.be.undefined; - expect(requests[0].data.quantx_user_consent_string).to.be.undefined; - }); - }); - - describe('interpretResponse', function () { - let bidderRequest = { - bidderCode: 'bidderCode', - bids: [] - } - - it('handles native request : should get correct bid response', function () { - const result = spec.interpretResponse({body: nativeServerResponse}, NATIVE_REQUEST) - expect(result[0]).to.have.property('cpm').equal(0.3) - expect(result[0]).to.have.property('width').to.be.below(2) - expect(result[0]).to.have.property('height').to.be.below(2) - expect(result[0]).to.have.property('mediaType').equal('native') - expect(result[0]).to.have.property('native') - }) - - it('should get correct bid response', function () { - const result = spec.interpretResponse({body: serverResponse}, REQUEST) - expect(result[0]).to.have.property('cpm').equal(0.3) - expect(result[0]).to.have.property('width').equal(300) - expect(result[0]).to.have.property('height').equal(250) - expect(result[0]).to.have.property('mediaType').equal('banner') - expect(result[0]).to.have.property('ad') - }) - - it('handles nobid responses', function () { - const nobidServerResponse = {bids: []} - const nobidResult = spec.interpretResponse({body: nobidServerResponse}, bidderRequest) - // console.log(nobidResult) - expect(nobidResult.length).to.equal(0) - }) - }) -}) diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js new file mode 100644 index 00000000000..560206681ee --- /dev/null +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai' +import { ENDPOINT, spec } from 'modules/qwarryBidAdapter.js' +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 + }] + } +} + +const BIDDER_BANNER_RESPONSE = { + 'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'banner', + 'adomain': ['test.com'] + }] +} + +const BIDDER_VIDEO_RESPONSE = { + 'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'video', + 'adomain': ['test.com'] + }] +} + +const BIDDER_NO_BID_RESPONSE = '' + +describe('qwarryBidAdapter', 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 true when required params found', function () { + expect(spec.isBidRequestValid(REQUEST)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, REQUEST) + delete bid.params.zoneToken + expect(spec.isBidRequestValid(bid)).to.equal(false) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [REQUEST] + const bidderRequest = spec.buildRequests(bidRequests, { + bidderRequestId: '123', + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + }, + refererInfo: { + referer: 'http://test.com/path.html' + } + }) + + it('sends bid request to ENDPOINT via POST', 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.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') + expect(bidderRequest.url).to.equal(ENDPOINT) + }) + }) + + describe('interpretResponse', function () { + it('handles banner request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_BANNER_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('
test
') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46d') + expect(result[0]).to.have.property('cpm').equal(900.5) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(640) + expect(result[0]).to.have.property('height').equal(480) + expect(result[0]).to.have.property('ttl').equal(300) + expect(result[0]).to.have.property('creativeId').equal(1) + 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 () { + const result = spec.interpretResponse({ body: BIDDER_VIDEO_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('vast') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46z') + expect(result[0]).to.have.property('cpm').equal(800.4) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(1024) + expect(result[0]).to.have.property('height').equal(768) + expect(result[0]).to.have.property('ttl').equal(200) + expect(result[0]).to.have.property('creativeId').equal(2) + 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('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 () { + let result = spec.interpretResponse({ body: undefined }, {}) + expect(result).to.deep.equal([]) + + result = spec.interpretResponse({ body: BIDDER_NO_BID_RESPONSE }, {}) + expect(result).to.deep.equal([]) + }) + }) + + describe('onBidWon', function () { + it('handles banner win: should get true', function () { + const win = BIDDER_BANNER_RESPONSE.prebidResponse[0] + const bidWonResult = spec.onBidWon(win) + + expect(bidWonResult).to.equal(true) + }) + }) +}) diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index c629daf3da5..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' } }, @@ -87,23 +102,50 @@ describe('radsAdapter', function () { 'auctionId': '1d1a030790a475' }]; + // Without gdprConsent let bidderRequest = { refererInfo: { referer: 'some_referrer.net' } } + // With gdprConsent + var bidderRequestGdprConsent = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + // without gdprConsent const request = spec.buildRequests(bidRequests, bidderRequest); 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 + const request2 = spec.buildRequests(bidRequests, bidderRequestGdprConsent); + 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('_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('_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'); }); }); @@ -119,7 +161,8 @@ describe('radsAdapter', function () { 'currency': 'EUR', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'adomain': ['bdomain'] } }; let serverVideoResponse = { @@ -147,7 +190,8 @@ describe('radsAdapter', function () { currency: 'EUR', netRevenue: true, ttl: 300, - ad: '' + ad: '', + meta: {advertiserDomains: ['bdomain']} }, { requestId: '23beaa6af6cdde', cpm: 0.5, @@ -159,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 () { @@ -175,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 () { @@ -193,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 () { @@ -203,4 +251,85 @@ describe('radsAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + 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/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index eb9077fac39..eefd7792a7c 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('ReadPeakAdapter', function() { params: { bidfloor: 5.0, publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77' + siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77', + tagId: 'test-tag-1' }, bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', @@ -104,7 +105,8 @@ describe('ReadPeakAdapter', function() { ver: '1.1' }, bidfloor: 5, - bidfloorcur: 'USD' + bidfloorcur: 'USD', + tagId: 'test-tag-1' } ], site: { @@ -177,20 +179,83 @@ describe('ReadPeakAdapter', function() { expect(data.id).to.equal(bidRequest.bidderRequestId); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.site).to.deep.equal({ - publisher: { - id: bidRequest.params.publisherId, - domain: 'http://localhost:9876' - }, - id: bidRequest.params.siteId, - page: bidderRequest.refererInfo.referer, - domain: parseUrl(bidderRequest.refererInfo.referer).hostname - }); + expect(data.imp[0].tagId).to.equal('test-tag-1'); + expect(data.site.publisher.id).to.equal(bidRequest.params.publisherId); + expect(data.site.id).to.equal(bidRequest.params.siteId); + expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); + expect(data.site.domain).to.equal(parseUrl(bidderRequest.refererInfo.referer).hostname); expect(data.device).to.deep.contain({ ua: navigator.userAgent, language: navigator.language }); expect(data.cur).to.deep.equal(['EUR']); + expect(data.user).to.be.undefined; + expect(data.regs).to.be.undefined; + }); + + it('should get bid floor from module', function() { + const floorModuleData = { + currency: 'USD', + floor: 3.2, + } + bidRequest.getFloor = function () { + return floorModuleData + } + const request = spec.buildRequests([bidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it('should send gdpr data when gdpr does not apply', function() { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: '' + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false + } + }); + }); + + it('should send gdpr data when gdpr applies', function() { + const tcString = 'sometcstring'; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString + } + } + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true + } + }); }); }); @@ -215,6 +280,9 @@ describe('ReadPeakAdapter', function() { currency: serverResponse.cur }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ['readpeak.com'], + }) expect(bidResponse.native.title).to.equal('Title'); expect(bidResponse.native.body).to.equal('Description'); expect(bidResponse.native.image).to.deep.equal({ diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js new file mode 100644 index 00000000000..b84aef15feb --- /dev/null +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -0,0 +1,160 @@ +import * as rtdModule from 'modules/rtdModule/index.js'; +import { config } from 'src/config.js'; +import * as sinon from 'sinon'; + +const getBidRequestDataSpy = sinon.spy(); + +const validSM = { + name: 'validSM', + init: () => { return true }, + getTargetingData: (adUnitsCodes) => { + return {'ad2': {'key': 'validSM'}} + }, + getBidRequestData: getBidRequestDataSpy +}; + +const validSMWait = { + name: 'validSMWait', + init: () => { return true }, + getTargetingData: (adUnitsCodes) => { + return {'ad1': {'key': 'validSMWait'}} + }, + getBidRequestData: getBidRequestDataSpy +}; + +const invalidSM = { + name: 'invalidSM' +}; + +const failureSM = { + name: 'failureSM', + init: () => { return false } +}; + +const nonConfSM = { + name: 'nonConfSM', + init: () => { return true } +}; + +const conf = { + 'realTimeData': { + 'auctionDelay': 100, + dataProviders: [ + { + 'name': 'validSMWait', + 'waitForIt': true, + }, + { + 'name': 'validSM', + 'waitForIt': false, + }, + { + 'name': 'invalidSM' + }, + { + 'name': 'failureSM' + }] + } +}; + +describe('Real time module', function () { + before(function () { + rtdModule.attachRealTimeDataProvider(validSM); + rtdModule.attachRealTimeDataProvider(invalidSM); + rtdModule.attachRealTimeDataProvider(failureSM); + rtdModule.attachRealTimeDataProvider(nonConfSM); + rtdModule.attachRealTimeDataProvider(validSMWait); + }); + + after(function () { + config.resetConfig(); + }); + + beforeEach(function () { + config.setConfig(conf); + }); + + it('should use only valid modules', function () { + rtdModule.init(config); + 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('deep merge object', function () { + const obj1 = { + id1: { + key: 'value', + key2: 'value2' + }, + id2: { + k: 'v' + } + }; + const obj2 = { + id1: { + key3: 'value3' + } + }; + const obj3 = { + id3: { + key: 'value' + } + }; + const expected = { + id1: { + key: 'value', + key2: 'value2', + key3: 'value3' + }, + id2: { + k: 'v' + }, + id3: { + key: 'value' + } + }; + + const merged = rtdModule.deepMerge([obj1, obj2, obj3]); + 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'} + } + ] + }; + + const expectedAdUnits = [ + { + code: 'ad1', + adserverTargeting: {key: 'validSMWait'} + }, + { + code: 'ad2', + adserverTargeting: { + preKey: 'preValue', + key: 'validSM' + } + } + ]; + + const adUnits = rtdModule.getAdUnitTargeting(auction); + assert.deepEqual(expectedAdUnits, adUnits) + done(); + }) +}); diff --git a/test/spec/modules/realTimeModule_spec.js b/test/spec/modules/realTimeModule_spec.js deleted file mode 100644 index ca149fe7a44..00000000000 --- a/test/spec/modules/realTimeModule_spec.js +++ /dev/null @@ -1,282 +0,0 @@ -import { - init, - requestBidsHook, - setTargetsAfterRequestBids, - deepMerge, - validateProviderDataForGPT -} from 'modules/rtdModule/index.js'; -import { - init as browsiInit, - addBrowsiTag, - isIdMatchingAdUnit, - setData, - getMacroId -} from 'modules/browsiRtdProvider.js'; -import { - init as audigentInit, - setData as setAudigentData -} from 'modules/audigentRtdProvider.js'; -import { config } from 'src/config.js'; -import { makeSlot } from '../integration/faker/googletag.js'; - -let expect = require('chai').expect; - -describe('Real time module', function () { - const conf = { - 'realTimeData': { - 'auctionDelay': 250, - dataProviders: [{ - 'name': 'browsi', - 'params': { - 'url': 'testUrl.com', - 'siteKey': 'testKey', - 'pubKey': 'testPub', - 'keyName': 'bv' - } - }, { - 'name': 'audigent' - }] - } - }; - - const predictions = { - p: { - 'browsiAd_2': { - 'w': [ - '/57778053/Browsi_Demo_Low', - '/57778053/Browsi_Demo_300x250' - ], - 'p': 0.07 - }, - 'browsiAd_1': { - 'w': [], - 'p': 0.06 - }, - 'browsiAd_3': { - 'w': [], - 'p': 0.53 - }, - 'browsiAd_4': { - 'w': [ - '/57778053/Browsi_Demo' - ], - 'p': 0.85 - } - } - }; - - const audigentSegments = { - audigent_segments: { 'a': 1, 'b': 2 } - } - - function getAdUnitMock(code = 'adUnit-code') { - return { - code, - mediaTypes: { banner: {}, native: {} }, - sizes: [[300, 200], [300, 600]], - bids: [{ bidder: 'sampleBidder', params: { placementId: 'banner-only-bidder' } }] - }; - } - - function createSlots() { - const slot1 = makeSlot({ code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1' }); - const slot2 = makeSlot({ code: '/57778053/Browsi', divId: 'browsiAd_1' }); - return [slot1, slot2]; - } - - describe('Real time module with browsi provider', function () { - afterEach(function () { - $$PREBID_GLOBAL$$.requestBids.removeAll(); - }); - - after(function () { - config.resetConfig(); - }); - - it('check module using bidsBackCallback', function () { - let adUnits1 = [getAdUnitMock('browsiAd_1')]; - let targeting = []; - init(config); - browsiInit(config); - config.setConfig(conf); - setData(predictions); - - // set slot - const slots = createSlots(); - window.googletag.pubads().setSlots(slots); - - function afterBidHook() { - slots.map(s => { - targeting = []; - s.getTargeting().map(value => { - targeting.push(Object.keys(value).toString()); - }); - }); - - expect(targeting.indexOf('bv')).to.be.greaterThan(-1); - } - setTargetsAfterRequestBids(afterBidHook, adUnits1, true); - }); - - it('check module using requestBidsHook', function () { - let adUnits1 = [getAdUnitMock('browsiAd_1')]; - let targeting = []; - let dataReceived = null; - - // set slot - const slotsB = createSlots(); - window.googletag.pubads().setSlots(slotsB); - - function afterBidHook(data) { - dataReceived = data; - slotsB.map(s => { - targeting = []; - s.getTargeting().map(value => { - targeting.push(Object.keys(value).toString()); - }); - }); - - expect(targeting.indexOf('bv')).to.be.greaterThan(-1); - dataReceived.adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.realTimeData).to.have.property('bv'); - }); - }); - } - requestBidsHook(afterBidHook, { adUnits: adUnits1 }); - }); - - it('check object deep merge', function () { - const obj1 = { - id1: { - key: 'value', - key2: 'value2' - }, - id2: { - k: 'v' - } - }; - const obj2 = { - id1: { - key3: 'value3' - } - }; - const obj3 = { - id3: { - key: 'value' - } - }; - const expected = { - id1: { - key: 'value', - key2: 'value2', - key3: 'value3' - }, - id2: { - k: 'v' - }, - id3: { - key: 'value' - } - }; - - const merged = deepMerge([obj1, obj2, obj3]); - assert.deepEqual(expected, merged); - }); - - it('check data validation for GPT targeting', function () { - // non strings values should be removed - const obj = { - valid: {'key': 'value'}, - invalid: {'key': ['value']}, - combine: { - 'a': 'value', - 'b': [] - } - }; - - const expected = { - valid: {'key': 'value'}, - invalid: {}, - combine: { - 'a': 'value', - } - }; - const validationResult = validateProviderDataForGPT(obj); - assert.deepEqual(expected, validationResult); - }); - - it('check browsi sub module', function () { - const script = addBrowsiTag('scriptUrl.com'); - expect(script.getAttribute('data-sitekey')).to.equal('testKey'); - expect(script.getAttribute('data-pubkey')).to.equal('testPub'); - expect(script.async).to.equal(true); - - const slots = createSlots(); - const test1 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_300x250']); // true - const test2 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true - const test3 = isIdMatchingAdUnit(slots[0], ['/57778053/Browsi_Demo_Low']); // false - const test4 = isIdMatchingAdUnit(slots[0], []); // true - - expect(test1).to.equal(true); - expect(test2).to.equal(true); - expect(test3).to.equal(false); - expect(test4).to.equal(true); - - // macro results - slots[0].setTargeting('test', ['test', 'value']); - // slot getTargeting doesn't act like GPT so we can't expect real value - const macroResult = getMacroId({p: '/'}, slots[0]); - expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA'); - - const macroResultB = getMacroId({}, slots[0]); - expect(macroResultB).to.equal('browsiAd_1'); - - const macroResultC = getMacroId({p: '', s: {s: 0, e: 1}}, slots[0]); - expect(macroResultC).to.equal('/'); - }) - }); - - describe('Real time module with Audigent provider', function () { - before(function () { - init(config); - audigentInit(config); - config.setConfig(conf); - setAudigentData(audigentSegments); - }); - - afterEach(function () { - $$PREBID_GLOBAL$$.requestBids.removeAll(); - config.resetConfig(); - }); - - it('check module using requestBidsHook', function () { - let adUnits1 = [getAdUnitMock('audigentAd_1')]; - let targeting = []; - let dataReceived = null; - - // set slot - const slotsB = createSlots(); - window.googletag.pubads().setSlots(slotsB); - - function afterBidHook(data) { - dataReceived = data; - slotsB.map(s => { - targeting = []; - s.getTargeting().map(value => { - targeting.push(Object.keys(value).toString()); - }); - }); - - dataReceived.adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.realTimeData).to.have.property('audigent_segments'); - expect(bid.realTimeData.audigent_segments).to.deep.equal(audigentSegments.audigent_segments); - }); - }); - } - - requestBidsHook(afterBidHook, { adUnits: adUnits1 }); - }); - }); -}); diff --git a/test/spec/modules/reconciliationRtdProvider_spec.js b/test/spec/modules/reconciliationRtdProvider_spec.js new file mode 100644 index 00000000000..6efe55ddf46 --- /dev/null +++ b/test/spec/modules/reconciliationRtdProvider_spec.js @@ -0,0 +1,221 @@ +import { + reconciliationSubmodule, + track, + getTopIFrameWin, + getSlotByWin +} from 'modules/reconciliationRtdProvider.js'; +import { makeSlot } from '../integration/faker/googletag.js'; +import * as utils from 'src/utils.js'; + +describe('Reconciliation Real time data submodule', function () { + const conf = { + dataProviders: [{ + 'name': 'reconciliation', + 'params': { + 'publisherMemberId': 'test_prebid_publisher' + }, + }] + }; + + let trackPostStub; + + beforeEach(function () { + trackPostStub = sinon.stub(track, 'trackPost'); + }); + + afterEach(function () { + trackPostStub.restore(); + }); + + describe('reconciliationSubmodule', function () { + describe('initialization', function () { + let utilsLogErrorSpy; + + before(function () { + utilsLogErrorSpy = sinon.spy(utils, 'logError'); + }); + + after(function () { + utils.logError.restore(); + }); + + it('successfully instantiates', function () { + expect(reconciliationSubmodule.init(conf.dataProviders[0])).to.equal(true); + }); + + it('should log error if initializied without parameters', function () { + expect(reconciliationSubmodule.init({'name': 'reconciliation', 'params': {}})).to.equal(true); + expect(utilsLogErrorSpy.calledOnce).to.be.true; + }); + }); + + describe('getData', function () { + it('should return data in proper format', function () { + makeSlot({code: '/reconciliationAdunit1', divId: 'reconciliationAd1'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['/reconciliationAdunit1']); + expect(targetingData['/reconciliationAdunit1'].RSDK_AUID).to.eql('/reconciliationAdunit1'); + expect(targetingData['/reconciliationAdunit1'].RSDK_ADID).to.be.a('string'); + }); + + it('should return unit path if called with divId', function () { + makeSlot({code: '/reconciliationAdunit2', divId: 'reconciliationAd2'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['reconciliationAd2']); + expect(targetingData['reconciliationAd2'].RSDK_AUID).to.eql('/reconciliationAdunit2'); + expect(targetingData['reconciliationAd2'].RSDK_ADID).to.be.a('string'); + }); + + it('should skip empty adUnit id', function () { + makeSlot({code: '/reconciliationAdunit3', divId: 'reconciliationAd3'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['reconciliationAd3', '']); + expect(targetingData).to.have.all.keys('reconciliationAd3'); + }); + }); + + describe('track events', function () { + it('should track init event with data', function () { + const adUnit = { + code: '/adunit' + }; + + reconciliationSubmodule.getTargetingData([adUnit.code]); + + expect(trackPostStub.calledOnce).to.be.true; + expect(trackPostStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/init'); + expect(trackPostStub.getCalls()[0].args[1].adUnits[0].adUnitId).to.eql(adUnit.code); + expect(trackPostStub.getCalls()[0].args[1].adUnits[0].adDeliveryId).be.a('string'); + expect(trackPostStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); + }); + }); + + describe('get topmost iframe', function () { + /** + * - top + * -- iframe.window <-- top iframe window + * --- iframe.window + * ---- iframe.window <-- win + */ + const mockFrameWin = (topWin, parentWin) => { + return { + top: topWin, + parent: parentWin + } + } + + it('should return null if called with null', function() { + expect(getTopIFrameWin(null)).to.be.null; + }); + + it('should return null if there is an error in frames chain', function() { + const topWin = {}; + const iframe1Win = mockFrameWin(topWin, null); // break chain + const iframe2Win = mockFrameWin(topWin, iframe1Win); + + expect(getTopIFrameWin(iframe1Win, topWin)).to.be.null; + }); + + it('should get the topmost iframe', function () { + const topWin = {}; + const iframe1Win = mockFrameWin(topWin, topWin); + const iframe2Win = mockFrameWin(topWin, iframe1Win); + + expect(getTopIFrameWin(iframe2Win, topWin)).to.eql(iframe1Win); + }); + }); + + describe('get slot by nested iframe window', function () { + it('should return the slot', function () { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAd'; + adSlotElement.appendChild(adSlotIframe); + document.body.appendChild(adSlotElement); + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + + expect(getSlotByWin(adSlotIframe.contentWindow)).to.eql(adSlot); + }); + + it('should return null if the slot is not found', function () { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAd'; + document.body.appendChild(adSlotElement); + document.body.appendChild(adSlotIframe); // iframe is not in ad slot + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + + expect(getSlotByWin(adSlotIframe.contentWindow)).to.be.null; + }); + }); + + describe('handle postMessage from Reconciliation Tag in ad iframe', function () { + it('should track impression pixel with parameters', function (done) { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAdMessage'; + adSlotElement.appendChild(adSlotIframe); + document.body.appendChild(adSlotElement); + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + // Fix targeting methods + adSlot.targeting = {}; + adSlot.setTargeting = function(key, value) { + this.targeting[key] = [value]; + }; + adSlot.getTargeting = function(key) { + return this.targeting[key]; + }; + + adSlot.setTargeting('RSDK_AUID', '/reconciliationAdunit'); + adSlot.setTargeting('RSDK_ADID', '12345'); + adSlotIframe.contentDocument.open(); + adSlotIframe.contentDocument.write(``); + adSlotIframe.contentDocument.close(); + + setTimeout(() => { + expect(trackPostStub.calledOnce).to.be.true; + expect(trackPostStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/pimp'); + expect(trackPostStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); + expect(trackPostStub.getCalls()[0].args[1].adDeliveryId).to.eql('12345'); + expect(trackPostStub.getCalls()[0].args[1].tagOwnerMemberId).to.eql('test_member_id'); ; + expect(trackPostStub.getCalls()[0].args[1].dataSources.length).to.eql(1); + expect(trackPostStub.getCalls()[0].args[1].dataRecipients.length).to.eql(2); + expect(trackPostStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); + done(); + }, 100); + }); + }); + }); +}); 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 cd4918460db..e372c67bb4e 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,16 +1,21 @@ 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'; const DEFAULT_USER_AGENT = window.navigator.userAgent; const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const relaido_uuid = 'hogehoge'; const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const storage = getStorageManager(); +storage.setCookie(UUID_KEY, relaido_uuid); + describe('RelaidoAdapter', function () { - const relaido_uuid = 'hogehoge'; let bidRequest; let bidderRequest; let serverResponse; @@ -55,7 +60,8 @@ describe('RelaidoAdapter', function () { uuid: relaido_uuid, vast: '', playerUrl: 'https://relaido/player.js', - syncUrl: 'https://relaido/sync.html' + syncUrl: 'https://relaido/sync.html', + adomain: ['relaido.co.jp', 'www.cmertv.co.jp'] } }; serverRequest = { @@ -65,7 +71,6 @@ describe('RelaidoAdapter', function () { height: bidRequest.mediaTypes.video.playerSize[0][1], mediaType: 'video', }; - localStorage.setItem(UUID_KEY, relaido_uuid); }); describe('spec.isBidRequestValid', function () { @@ -86,10 +91,58 @@ describe('RelaidoAdapter', function () { setUADefault(); }); - it('should return false when the uuid are missing', function () { - localStorage.removeItem(UUID_KEY); - const result = !!(utils.isSafariBrowser()); - expect(spec.isBidRequestValid(bidRequest)).to.equal(result); + it('should return false when missing 300x250 over and 1x1 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [100, 100], + [300, 100] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + setUADefault(); + }); + + it('should return true when 300x250 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + + it('should return true when 1x1 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [1, 1] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + + it('should return true when 300x250 over by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [100, 100], + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); }); it('should return false when the placementId params are missing', function () { @@ -157,10 +210,18 @@ describe('RelaidoAdapter', function () { }); 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] ] } }; @@ -170,6 +231,31 @@ describe('RelaidoAdapter', function () { expect(request.mediaType).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); + expect(bidRequests).to.have.lengthOf(1); + const request = bidRequests[0]; + + // eslint-disable-next-line no-console + console.log(bidRequests); + 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); @@ -192,6 +278,8 @@ describe('RelaidoAdapter', function () { 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.meta.advertiserDomains).to.equal(serverResponse.body.adomain); + expect(response.meta.mediaType).to.equal(VIDEO); expect(response.ad).to.be.undefined; }); @@ -234,7 +322,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' }]); }); @@ -242,7 +330,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' }]); }); @@ -251,7 +339,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' }]); }); diff --git a/test/spec/modules/relevantAnalyticsAdapter_spec.js b/test/spec/modules/relevantAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3e31db2d7dc --- /dev/null +++ b/test/spec/modules/relevantAnalyticsAdapter_spec.js @@ -0,0 +1,43 @@ +import relevantAnalytics from '../../../modules/relevantAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' +import { expect } from 'chai'; + +describe('Relevant Analytics Adapter', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'relevant' + }); + }); + + afterEach(() => { + relevantAnalytics.disableAnalytics(); + }); + + it('should pass all events to the global array', () => { + // Given + const testEvents = [ + { ev: constants.EVENTS.AUCTION_INIT, args: { test: 1 } }, + { ev: constants.EVENTS.BID_REQUESTED, args: { test: 2 } }, + ]; + + // When + testEvents.forEach(({ ev, args }) => ( + events.emit(ev, args) + )); + + // Then + const eventQueue = (window.relevantDigital || {}).pbEventLog; + expect(eventQueue).to.be.an('array'); + expect(eventQueue.length).to.be.at.least(testEvents.length); + + // The last events should be our test events + const myEvents = eventQueue.slice(-testEvents.length); + testEvents.forEach(({ ev, args }, idx) => { + const actualEvent = myEvents[idx]; + expect(actualEvent.ev).to.eql(ev); + expect(actualEvent.args).to.eql(args); + }); + }); +}); 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 82% rename from test/spec/modules/smartrtbBidAdapter_spec.js rename to test/spec/modules/resetdigitalBidAdapter_spec.js index cb5ceee0870..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,16 +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 alias smrtb', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true + it('should return a bidder code of resetdigital', function () { + expect(spec.code).to.equal('resetdigital') }) }) @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + 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 d9342332e61..e0e58fc89cd 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -157,6 +157,7 @@ describe('rhythmone adapter tests', function () { expect(bid.width).to.equal(800); expect(bid.height).to.equal(600); expect(bid.vastUrl).to.equal('https://testdomain/rmp/placementid/0/path?reqId=1636037'); + expect(bid.meta.advertiserDomains).to.deep.equal(['test.com']); expect(bid.mediaType).to.equal('video'); expect(bid.creativeId).to.equal('cr-vid'); expect(bid.currency).to.equal('USD'); @@ -443,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', @@ -468,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 f18e67a3ac5..00ae55823b0 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -4,7 +4,6 @@ import { spec } from 'modules/richaudienceBidAdapter.js'; import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; describe('Richaudience adapter tests', function () { var DEFAULT_PARAMS_NEW_SIZES = [{ @@ -20,7 +19,8 @@ describe('Richaudience adapter tests', function () { params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site' + supplyType: 'site', + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -156,8 +149,8 @@ describe('Richaudience adapter tests', function () { netRevenue: true, currency: 'USD', ttl: 300, - dealId: 'dealId' - + dealId: 'dealId', + adomain: 'richaudience.com' } }; @@ -172,7 +165,8 @@ describe('Richaudience adapter tests', function () { currency: 'USD', ttl: 300, vastXML: '', - dealId: 'dealId' + dealId: 'dealId', + adomain: 'richaudience.com' } }; @@ -240,6 +234,9 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); expect(requestContent).to.have.property('timeout').and.to.equal(3000); expect(requestContent).to.have.property('numIframes').and.to.equal(0); + expect(typeof requestContent.scr_rsl === 'string') + expect(typeof requestContent.cpuc === 'number') + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -259,8 +256,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -278,6 +273,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -348,34 +344,49 @@ describe('Richaudience adapter tests', function () { }); describe('UID test', function () { - pbjs.setConfig({ + config.setConfig({ consentManagement: { cmpApi: 'iab', timeout: 5000, allowAuctionWithoutConsent: true + }, + userSync: { + userIds: [{ + name: 'id5Id', + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this + }, + storage: { + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules } }); it('Verify build id5', function () { + var request; DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 'id5-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user).to.deep.equal([{ - 'userId': 'id5-user-id', - 'source': 'id5-sync.com' - }]); + expect(requestContent.user.eids).to.equal(undefined); - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 1; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); expect(requestContent.user.eids).to.equal(undefined); - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = []; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); requestContent = JSON.parse(request[0].data); expect(requestContent.user.eids).to.equal(undefined); @@ -590,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 () { @@ -605,9 +617,20 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + 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 () { @@ -622,6 +645,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + 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: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -638,6 +690,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -699,78 +761,373 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); - it('Verifies user syncs iframe', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + describe('userSync', function () { + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); - expect(syncs).to.have.lengthOf(0); - syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - pbjs.setConfig({ - consentManagement: { - cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true, + syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - } + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); - }); - it('Verifies user syncs image', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + }) }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js new file mode 100644 index 00000000000..61b307eef21 --- /dev/null +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -0,0 +1,405 @@ +import { expect } from 'chai'; +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 { deepClone } from 'src/utils.js'; + +const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const TTL = 360; + +describe('riseAdapter', 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': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'rise', + } + const placementId = '12345678'; + + 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.placement_id).to.equal(placementId); + } + }); + + 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 bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', 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('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + 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,,,,'); + } + }); + + it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = 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 = 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 () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD', + adomain: ['abc.com'] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO, + meta: { + advertiserDomains: ['abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); 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 efaed87dd15..d6bee26d73b 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -51,38 +51,7 @@ describe('RTBHouseAdapter', () => { }); describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'rtbhouse', - 'params': { - 'publisherId': 'PREBID_TEST', - 'region': 'prebid-eu', - 'test': 1 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]], - } - }, - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': 'example-transaction-id', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'directseller.com', - 'sid': '00001', - 'rid': 'BidRequest1', - 'hp': 1 - } - ] - } - } - ]; + let bidRequests; const bidderRequest = { 'refererInfo': { 'numIframes': 0, @@ -92,6 +61,41 @@ describe('RTBHouseAdapter', () => { } }; + beforeEach(() => { + bidRequests = [ + { + 'bidder': 'rtbhouse', + 'params': { + 'publisherId': 'PREBID_TEST', + 'region': 'prebid-eu', + 'test': 1 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]], + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': 'example-transaction-id', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'directseller.com', + 'sid': '00001', + 'rid': 'BidRequest1', + 'hp': 1 + } + ] + } + } + ]; + }); + it('should build test param into the request', () => { let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; expect(JSON.parse(builtTestRequest).test).to.equal(1); @@ -177,6 +181,23 @@ describe('RTBHouseAdapter', () => { expect(data.source.tid).to.equal('example-transaction-id'); }); + it('should include bidfloor from floor module if avaiable', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].getFloor = () => ({floor: 1.22}); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].bidfloor).to.equal(1.22) + }); + + it('should use bidfloor from floor module if both floor module and bid floor avaiable', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].getFloor = () => ({floor: 1.22}); + bidRequest[0].params.bidfloor = 0.01; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].bidfloor).to.equal(1.22) + }); + it('should include bidfloor in request if available', () => { const bidRequest = Object.assign([], bidRequests); bidRequest[0].params.bidfloor = 0.01; @@ -185,11 +206,11 @@ describe('RTBHouseAdapter', () => { expect(data.imp[0].bidfloor).to.equal(0.01) }); - it('should include source.ext.schain in request', () => { + it('should include schain in request', () => { const bidRequest = Object.assign([], bidRequests); const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); - expect(data.source.ext.schain).to.deep.equal({ + expect(data.ext.schain).to.deep.equal({ 'ver': '1.0', 'complete': 1, 'nodes': [ @@ -203,6 +224,13 @@ describe('RTBHouseAdapter', () => { }); }); + it('should include source.tid in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source).to.have.deep.property('tid'); + }); + it('should not include invalid schain', () => { const bidRequest = Object.assign([], bidRequests); bidRequest[0].schain = { @@ -424,6 +452,7 @@ describe('RTBHouseAdapter', () => { 'mediaType': 'banner', 'currency': 'USD', 'ttl': 300, + 'meta': { advertiserDomains: ['rtbhouse.com'] }, 'netRevenue': true } ]; @@ -486,6 +515,7 @@ describe('RTBHouseAdapter', () => { it('should contain native assets in valid format', () => { const bids = spec.interpretResponse({body: response}, {}); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['rtbhouse.com']); expect(bids[0].native).to.deep.equal({ title: 'Title text', clickUrl: encodeURIComponent('https://example.com'), diff --git a/test/spec/modules/rtbsapeBidAdapter_spec.js b/test/spec/modules/rtbsapeBidAdapter_spec.js new file mode 100644 index 00000000000..eea9e51b1a9 --- /dev/null +++ b/test/spec/modules/rtbsapeBidAdapter_spec.js @@ -0,0 +1,209 @@ +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'; + +describe('rtbsapeBidAdapterTests', function () { + describe('isBidRequestValid', function () { + it('valid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {placeId: 4321}})).to.equal(true); + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {video: true}, params: {placeId: 4321}})).to.equal(true); + }); + + it('invalid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {}})).to.equal(false); + expect(spec.isBidRequestValid({bidder: 'rtbsape', params: {placeId: 4321}})).to.equal(false); + }); + }); + + it('buildRequests', function () { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'rtbsape', + params: {placeId: 4321}, + sizes: [[240, 400]] + }]; + let bidderRequest = { + auctionId: '2e208334-cafe-4c2c-b06b-f055ff876852', + bidderRequestId: '1392d0aa613366', + refererInfo: {} + }; + let request = spec.buildRequests(bidRequestData, bidderRequest); + expect(request.data.auctionId).to.equal('2e208334-cafe-4c2c-b06b-f055ff876852'); + expect(request.data.requestId).to.equal('1392d0aa613366'); + expect(request.data.bids[0].bidId).to.equal('bid1234'); + expect(request.data.timezone).to.not.equal(undefined); + }); + + describe('interpretResponse', function () { + it('banner', function () { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + cpm: 2.21, + currency: 'RUB', + width: 240, + height: 400, + netRevenue: true, + ad: 'Ad html', + 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.21); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(240); + expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid1234'); + expect(bid.ad).to.equal('Ad html'); + }); + + describe('video (outstream)', function () { + let bid; + + before(() => { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + adUnitCode: 'ad-bid1234', + cpm: 3.32, + currency: 'RUB', + width: 600, + height: 340, + netRevenue: true, + vastUrl: 'https://cdn-rtb.sape.ru/vast/4321.xml', + meta: { + advertiserDomains: ['rtb.sape.ru'], + mediaType: 'video' + } + }] + } + }; + let serverRequest = { + data: { + bids: [{ + bidId: 'bid1234', + adUnitCode: 'ad-bid1234', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + placeId: 4321, + video: { + playerMuted: false + } + } + }] + } + }; + let bids = spec.interpretResponse(serverResponse, serverRequest); + expect(bids).to.have.lengthOf(1); + bid = bids[0]; + }); + + it('should add renderer', () => { + expect(bid).to.have.own.property('renderer'); + expect(bid.renderer).to.be.instanceof(Renderer); + expect(bid.renderer.url).to.equal('https://cdn-rtb.sape.ru/js/player.js'); + expect(bid.playerMuted).to.equal(false); + }); + + it('should create player instance', () => { + let spy = false; + + window.sapeRtbPlayerHandler = function (id, w, h, m) { + const player = {addSlot: () => [id, w, h, m]} + expect(spy).to.equal(false); + spy = sinon.spy(player, 'addSlot'); + return player; + }; + + executeRenderer(bid.renderer, bid); + bid.renderer.callback(); + expect(spy).to.not.equal(false); + expect(spy.called).to.be.true; + + const spyCall = spy.getCall(0); + expect(spyCall.args[0].url).to.be.equal('https://cdn-rtb.sape.ru/vast/4321.xml'); + expect(spyCall.returnValue[0]).to.be.equal('ad-bid1234'); + expect(spyCall.returnValue[1]).to.be.equal(600); + expect(spyCall.returnValue[2]).to.be.equal(340); + 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 () { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.an('array').that.to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({type: 'iframe', url: 'https://www.acint.net/mc/?dp=141'}); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon({cpm: '2.21', nurl: 'https://ssp-rtb.sape.ru/track?event=win'}); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon({cpm: '2.21'}); + expect(utils.triggerPixel.called).to.equal(false); + }); + }); +}); 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 1639389fe24..a9909f8a964 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -2,16 +2,17 @@ import rubiconAnalyticsAdapter, { SEND_TIMEOUT, parseBidResponse, getHostNameFromReferer, + storage, + rubiConf, } from 'modules/rubiconAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; - +import * as mockGpt from '../integration/faker/googletag.js'; import { setConfig, addBidResponseHook, } from 'modules/currency.js'; - let Ajv = require('ajv'); let schema = require('./rubiconAnalyticsSchema.json'); let ajv = new Ajv({ @@ -110,10 +111,85 @@ const BID2 = Object.assign({}, BID, { } }); +const BID3 = Object.assign({}, BID, { + adUnitCode: '/19968336/siderail-tag1', + bidId: '5fg6hyy4r879f0', + adId: 'fake_ad_id', + requestId: '5fg6hyy4r879f0', + width: 300, + height: 250, + mediaType: 'banner', + cpm: 2.01, + source: 'server', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/siderail-tag1', + 'rpfl_14062': '15_tier0200' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '5fg6hyy4r879f0', + 'hb_pb': '2.00', + 'hb_size': '300x250', + 'hb_source': 'server' + } +}); + +const BID4 = Object.assign({}, BID, { + adUnitCode: '/19968336/header-bid-tag1', + bidId: '3bd4ebb1c900e2', + adId: 'fake_ad_id', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + source: 'server', + pbsBidId: 'zzzz-yyyy-xxxx-wwww', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/header-bid-tag1', + 'rpfl_14062': '2_tier0100' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + } +}); + +const floorMinRequest = { + 'bidder': 'rubicon', + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918', + 'userId': '12346', + 'keywords': ['a', 'b', 'c'], + 'inventory': {'rating': '4-star', 'prodtype': 'tech'}, + 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'position': 'atf' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': '/19968336/siderail-tag1', + 'transactionId': 'c435626g-9e3f-401a-bee1-d56aec29a1d4', + 'sizes': [[300, 250]], + 'bidId': '5fg6hyy4r879f0', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' +}; + const MOCK = { SET_TARGETING: { [BID.adUnitCode]: BID.adserverTargeting, - [BID2.adUnitCode]: BID2.adserverTargeting + [BID2.adUnitCode]: BID2.adserverTargeting, + [BID3.adUnitCode]: BID3.adserverTargeting }, AUCTION_INIT: { 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', @@ -198,13 +274,19 @@ 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]], 'bidId': '2ecff0db240757', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client' }, { 'bidder': 'rubicon', @@ -228,7 +310,8 @@ const MOCK = { 'sizes': [[1000, 300], [970, 250], [728, 90]], 'bidId': '3bd4ebb1c900e2', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 's2s' } ], 'auctionStart': 1519149536560, @@ -240,7 +323,8 @@ const MOCK = { }, BID_RESPONSE: [ BID, - BID2 + BID2, + BID3 ], AUCTION_END: { 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' @@ -251,14 +335,21 @@ const MOCK = { }), Object.assign({}, BID2, { 'status': 'rendered' + }), + Object.assign({}, BID3, { + 'status': 'rendered' }) ], BIDDER_DONE: { 'bidderCode': 'rubicon', + 'serverResponseTimeMs': 42, 'bids': [ BID, Object.assign({}, BID2, { 'serverResponseTimeMs': 42, + }), + Object.assign({}, BID3, { + 'serverResponseTimeMs': 55, }) ] }, @@ -272,14 +363,28 @@ const MOCK = { ] }; +const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; + const ANALYTICS_MESSAGE = { - 'eventTimeMillis': 1519767013781, + 'channel': 'web', 'integration': 'pbjs', 'version': '$prebid.version$', 'referrerUri': 'http://www.test.com/page.html', + 'session': { + 'expires': 1519788613781, + 'id': STUBBED_UUID, + 'start': 1519767013781 + }, + 'timestamps': { + 'auctionEnded': 1519767013781, + 'eventTime': 1519767013781, + 'prebidLoaded': rubiconAnalyticsAdapter.MODULE_INITIALIZED_TIME + }, + 'trigger': 'allBidWons', 'referrerHostname': 'www.test.com', 'auctions': [ { + 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'clientTimeoutMillis': 3000, 'serverTimeoutMillis': 1000, 'accountId': 1001, @@ -463,16 +568,23 @@ const ANALYTICS_MESSAGE = { 'bidwonStatus': 'success' } ], - 'wrapperName': '10000_fakewrapper_test' + 'wrapper': { + 'name': '10000_fakewrapper_test' + } }; -function performStandardAuction() { +function performStandardAuction(gptEvents) { 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); + + if (gptEvents && gptEvents.length) { + gptEvents.forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); + } + events.emit(SET_TARGETING, MOCK.SET_TARGETING); events.emit(BID_WON, MOCK.BID_WON[0]); events.emit(BID_WON, MOCK.BID_WON[1]); @@ -481,12 +593,20 @@ function performStandardAuction() { describe('rubicon analytics adapter', function () { let sandbox; let clock; - + let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; beforeEach(function () { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + mockGpt.disable(); sandbox = sinon.sandbox.create(); + localStorageIsEnabledStub.returns(true); + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'generateUUID').returns(STUBBED_UUID); + clock = sandbox.useFakeTimers(1519767013781); rubiconAnalyticsAdapter.referrerHostname = ''; @@ -505,6 +625,10 @@ describe('rubicon analytics adapter', function () { afterEach(function () { sandbox.restore(); config.resetConfig(); + mockGpt.enable(); + getDataFromLocalStorageStub.restore(); + setDataInLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); }); it('should require accountId', function () { @@ -531,6 +655,76 @@ describe('rubicon analytics adapter', function () { expect(utils.logError.called).to.equal(true); }); + 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}}); + expect(utils.generateUUID.called).to.equal(true); + }); + it('should merge in and preserve older set configs', function () { + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + } + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + }, + updatePageView: true + }); + + // update it with stuff + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' + } + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb', + link: 'email' + }, + updatePageView: true + }); + + // overwriting specific edge keys should update them + config.setConfig({ + rubicon: { + fpkvs: { + link: 'iMessage', + source: 'twitter' + } + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + link: 'iMessage', + source: 'twitter' + }, + updatePageView: true + }); + }); + }); + describe('sampling', function () { beforeEach(function () { sandbox.stub(Math, 'random').returns(0.08); @@ -659,14 +853,173 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(ANALYTICS_MESSAGE); }); - it('should capture price floor information correctly', function () { + 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); + + // mock bid response with playerWidth and playerHeight (NO width and height) + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + delete bidResponse1.width; + delete bidResponse1.height; + bidResponse1.playerWidth = 640; + bidResponse1.playerHeight = 480; + + // mock bid response with no width height or playerwidth playerheight + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + delete bidResponse2.width; + delete bidResponse2.height; + delete bidResponse2.playerWidth; + delete bidResponse2.playerHeight; + + 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.dimensions).to.deep.equal({ + width: 640, + height: 480 + }); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); + }); + + it('should pass along adomians correctly', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // 1 adomains + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: ['magnite.com'] + } + + // two adomains + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: ['prebid.org', 'magnite.com'] + } + + // make sure we only pass max 10 adomains + bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] + + 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(['magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); + }); + + it('should NOT pass along adomians correctly when edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // empty => nothing + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [] + } + + // not array => nothing + 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.be.undefined; + 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 = { skipped: false, modelVersion: 'someModelName', + modelWeight: 10, + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 15, - fetchStatus: 'error' + fetchStatus: 'error', + floorProvider: provider }; let flooredResponse = { ...BID, @@ -713,50 +1066,714 @@ describe('rubicon analytics adapter', function () { } }; + let floorMinResponse = { + ...BID3, + floorData: { + floorValue: 1.5, + floorRuleValue: 1, + floorRule: '12345/entertainment|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 2.00, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + matchedFields: { + gptSlot: '12345/entertainment', + mediaType: 'banner' + } + } + }; + + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids.push(floorMinRequest) + // spoof the auction with just our duplicates events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_REQUESTED, bidRequest); events.emit(BID_RESPONSE, flooredResponse); events.emit(BID_RESPONSE, notFlooredResponse); + events.emit(BID_RESPONSE, floorMinResponse); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.SET_TARGETING); events.emit(BID_WON, MOCK.BID_WON[1]); + events.emit(BID_WON, MOCK.BID_WON[2]); clock.tick(SEND_TIMEOUT + 1000); expect(server.requests.length).to.equal(1); let message = JSON.parse(server.requests[0].requestBody); validate(message); + return message; + } + + it('should capture price floor information correctly', function () { + let message = performFloorAuction('rubicon'); // verify our floor stuff is passed // top level floor info expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelWeight: 10, + modelTimestamp: 1606772895, skipped: false, enforcement: true, dealsEnforced: false, skipRate: 15, - fetchStatus: 'error' + fetchStatus: 'error', + provider: 'rubicon' }); // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports'); + expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); // since no other bids, we set adUnit status to no-bid expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected'); + expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); // if bid rejected should take cpmAfterAdjustments val expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].adSlot).to.equal('12345/news'); + expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); // top level adUnit status is success expect(message.auctions[0].adUnits[1].status).to.equal('success'); // second adUnits bid is success expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[2].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); + }); + + it('should still send floor info if provider is not rubicon', function () { + let message = performFloorAuction('randomProvider'); + + // verify our floor stuff is passed + // top level floor info + expect(message.auctions[0].floors).to.deep.equal({ + location: 'setConfig', + modelName: 'someModelName', + modelWeight: 10, + modelTimestamp: 1606772895, + skipped: false, + enforcement: true, + dealsEnforced: false, + skipRate: 15, + fetchStatus: 'error', + provider: 'randomProvider' + }); + // first adUnit's adSlot + expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); + // since no other bids, we set adUnit status to no-bid + expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); + // first adUnits bid is rejected + expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); + // if bid rejected should take cpmAfterAdjustments val + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[1].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[2].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); + }); + + describe('with session handling', function () { + const expectedPvid = STUBBED_UUID.slice(0, 8); + beforeEach(function () { + config.setConfig({rubicon: {updatePageView: true}}); + }); + + it('should not log any session data if local storage is not enabled', function () { + localStorageIsEnabledStub.returns(false); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.session; + delete expectedMessage.fpkvs; + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.equal('//localhost:9999/event'); + + let message = JSON.parse(request.requestBody); + validate(message); + + expect(message).to.deep.equal(expectedMessage); + }); + + it('should should pass along custom rubicon kv and pvid when defined', function () { + config.setConfig({rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + {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'} + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + 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]'} + ] + 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'}); + + config.setConfig({rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + {key: 'source', value: 'other'}, + {key: 'link', value: 'email'} + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + }); + + it('should pick up existing localStorage and use its values', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519766113781, // 15 mins before "now" + expires: 1519787713781, // six hours later + lastSeen: 1519766113781, + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + 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]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session = { + id: '987654', + start: 1519766113781, + expires: 1519787713781, + pvid: expectedPvid + } + expectedMessage.fpkvs = [ + {key: 'source', value: 'tw'}, + {key: 'link', value: 'email'} + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: '987654', // should have stayed same + start: 1519766113781, // should have stayed same + expires: 1519787713781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { source: 'tw', link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should overwrite matching localstorge value and use its remaining values', function () { + sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=fb&utm_click=dog'}); + + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519766113781, // 15 mins before "now" + expires: 1519787713781, // six hours later + lastSeen: 1519766113781, + fpkvs: { source: 'tw', link: 'email' } + }; + getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + 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]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session = { + id: '987654', + start: 1519766113781, + expires: 1519787713781, + pvid: expectedPvid + } + expectedMessage.fpkvs = [ + {key: 'source', value: 'fb'}, + {key: 'link', value: 'email'}, + {key: 'click', value: 'dog'} + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: '987654', // should have stayed same + start: 1519766113781, // should have stayed same + expires: 1519787713781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should throw out session if lastSeen > 30 mins ago and create new one', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519764313781, // 45 mins before "now" + expires: 1519785913781, // six hours later + lastSeen: 1519764313781, // 45 mins before "now" + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + 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]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid + expectedMessage.session.pvid = expectedPvid; + + // the saved fpkvs should have been thrown out since session expired + expectedMessage.fpkvs = [ + {key: 'link', value: 'email'} + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: STUBBED_UUID, // should have stayed same + start: 1519767013781, // should have stayed same + expires: 1519788613781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should throw out session if past expires time and create new one', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519745353781, // 6 hours before "expires" + expires: 1519766953781, // little more than six hours ago + lastSeen: 1519767008781, // 5 seconds ago + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + 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]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid + expectedMessage.session.pvid = expectedPvid; + + // the saved fpkvs should have been thrown out since session expired + expectedMessage.fpkvs = [ + {key: 'link', value: 'email'} + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: STUBBED_UUID, // should have stayed same + start: 1519767013781, // should have stayed same + expires: 1519788613781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + }); + describe('with googletag enabled', function () { + let gptSlot0, gptSlot1; + let gptSlotRenderEnded0, gptSlotRenderEnded1; + beforeEach(function () { + mockGpt.enable(); + gptSlot0 = mockGpt.makeSlot({code: '/19968336/header-bid-tag-0'}); + gptSlotRenderEnded0 = { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot0, + isEmpty: false, + advertiserId: 1111, + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 + } + }; + + gptSlot1 = mockGpt.makeSlot({code: '/19968336/header-bid-tag1'}); + gptSlotRenderEnded1 = { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 4444, + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 + } + }; + }); + + afterEach(function () { + mockGpt.disable(); + }); + + it('should add necessary gam information if gpt is enabled and slotRender event emmited', function () { + performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should handle empty gam renders', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: true + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + isSlotEmpty: true, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should still add gam ids if falsy', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 0, + lineItemId: 0, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should correctly set adUnit for associated slots', function () { + performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should send request when waitForGamSlots is present but no bidWons are sent', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true, + } + }); + 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); + + // should send if just slotRenderEnded is emmitted for both + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.bidsWon; // should not be any of these + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expectedMessage.trigger = 'gam'; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should delay the event call depending on analyticsEventDelay config', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true, + analyticsEventDelay: 2000 + } + }); + 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); + + // should send if just slotRenderEnded is emmitted for both + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); + + // Should not be sent until delay + expect(server.requests.length).to.equal(0); + + // tick the clock and it should fire + clock.tick(2000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + let expectedGam0 = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + let expectedGam1 = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); + expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); + }); }); it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { @@ -793,6 +1810,73 @@ describe('rubicon analytics adapter', function () { expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); }); + it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + }); + + it('should correctly generate new bidId if it is 0', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + seatBidResponse.pbsBidId = '0'; + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); + expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); + }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { // Only want one bid request in our mock auction let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); @@ -890,6 +1974,65 @@ describe('rubicon analytics adapter', function () { expect(timedOutBid).to.not.have.property('bidResponse'); }); + it('should pass aupName as pattern', function () { + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids[0].ortb2Imp = { + ext: { + data: { + aupname: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' + } + } + }; + bidRequest.bids[1].ortb2Imp = { + ext: { + data: { + aupname: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' + } + } + }; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, bidRequest); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + 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); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); + expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); + }); + + it('should pass bidderDetail for multibid auctions', function () { + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse.targetingBidder = 'rubi2'; + bidResponse.originalRequestId = bidResponse.requestId; + bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; + + 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, bidResponse); + 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]); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + + expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); + expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); + }); + it('should successfully convert bid price to USD in parseBidResponse', function () { // Set the rates setConfig({ @@ -921,12 +2064,9 @@ describe('rubicon analytics adapter', function () { describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { - sandbox.stub(config, 'getConfig').callsFake(function (key) { - const config = { - 'rubicon.int_type': 'testType' - }; - return config[key]; - }); + config.setConfig({rubicon: { + int_type: 'testType' + }}) rubiconAnalyticsAdapter.enableAnalytics({ options: { @@ -946,6 +2086,36 @@ 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' + }}); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_wrapperName_exp.4', + family: '1001_wrapperName', + rule: 'na-mobile' + }); + + rubiconAnalyticsAdapter.disableAnalytics(); + }); + }); + it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json index 16cca629d8c..a8fdeae5268 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.", @@ -34,6 +29,53 @@ "type": "string", "description": "Version of Prebid.js responsible for the auctions contained within." }, + "fpkvs": { + "type": "array", + "description": "List of any dynamic key value pairs set by publisher.", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "session": { + "type": "object", + "description": "The session information for a given event", + "required": [ + "id", + "start", + "expires" + ], + "properties": { + "id": { + "type": "string", + "description": "UUID of session." + }, + "start": { + "type": "integer", + "description": "Unix timestamp of time of creation for this session in milliseconds." + }, + "expires": { + "type": "integer", + "description": "Unix timestamp of the maximum allowed time in milliseconds of the session." + }, + "pvid": { + "type": "string", + "description": "id to track page view." + } + } + }, "auctions": { "type": "array", "minItems": 1, @@ -125,6 +167,9 @@ "zoneId": { "type": "number", "description": "The Rubicon zoneId associated with this adUnit - Removed if null" + }, + "gam": { + "$ref": "#/definitions/gam" } } } @@ -197,6 +242,31 @@ } }, "definitions": { + "gam": { + "type": "object", + "description": "The gam information for a given ad unit", + "required": [ + "adSlot" + ], + "properties": { + "adSlot": { + "type": "string" + }, + "advertiserId": { + "type": "integer" + }, + "creativeId": { + "type": "integer" + }, + "LineItemId": { + "type": "integer" + }, + "isSlotEmpty": { + "type": "boolean", + "enum": [true] + } + } + }, "adserverTargeting": { "type": "object", "description": "The adserverTargeting key/value pairs", @@ -293,7 +363,8 @@ "success", "no-bid", "error", - "rejected" + "rejected-gdpr", + "rejected-ipf" ] }, "error": { @@ -333,7 +404,6 @@ "bidResponse": { "type": "object", "required": [ - "dimensions", "mediaType", "bidPriceUSD" ], diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 11ed17df5f8..6210640f79f 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,10 +1,17 @@ import {expect} from 'chai'; -import adapterManager from 'src/adapterManager.js'; -import {spec, getPriceGranularity, masSizeOrdering, resetUserSync, hasVideoMediaType, FASTLANE_ENDPOINT} from 'modules/rubiconBidAdapter.js'; +import { + spec, + getPriceGranularity, + masSizeOrdering, + resetUserSync, + hasVideoMediaType, + resetRubiConf +} from 'modules/rubiconBidAdapter.js'; import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import find from 'core-js-pure/features/array/find.js'; +import {createEidsArray} from 'modules/userId/eids.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -13,7 +20,8 @@ describe('the rubicon adapter', function () { let sandbox, bidderRequest, sizeMap, - getFloorResponse; + getFloorResponse, + logErrorSpy; /** * @typedef {Object} sizeMapConverted @@ -136,7 +144,7 @@ describe('the rubicon adapter', function () { 'targeting': [ { 'key': getProp('targeting_key', `rpfl_${i}`), - 'values': [ '43_tier_all_test' ] + 'values': ['43_tier_all_test'] } ] }; @@ -219,12 +227,27 @@ describe('the rubicon adapter', function () { 'size_id': 201, }; bid.userId = { - lipb: { - lipbid: '0000-1111-2222-3333', - segments: ['segA', 'segB'] - }, - idl_env: '1111-2222-3333-4444' + lipb: {lipbid: '0000-1111-2222-3333', segments: ['segA', 'segB']}, + idl_env: '1111-2222-3333-4444', + tdid: '3000', + pubcid: '4000', + pubProvidedId: [{ + source: 'example.com', + uids: [{ + id: '333333', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: '4444444' + }] + }], + criteoId: '1111', }; + bid.userIdAsEids = createEidsArray(bid.userId); bid.storedAuctionResponse = 11111; } @@ -272,6 +295,7 @@ describe('the rubicon adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + logErrorSpy = sinon.spy(utils, 'logError'); getFloorResponse = {}; bidderRequest = { bidderCode: 'rubicon', @@ -343,6 +367,10 @@ describe('the rubicon adapter', function () { afterEach(function () { sandbox.restore(); + utils.logError.restore(); + config.resetConfig(); + resetRubiConf(); + delete $$PREBID_GLOBAL$$.installedModules; }); describe('MAS mapping / ordering', function () { @@ -368,7 +396,10 @@ describe('the rubicon adapter', function () { describe('to fastlane', function () { it('should make a well-formed request object', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let duplicate = Object.assign(bidderRequest); + duplicate.bids[0].params.floor = 0.01; + + let [request] = spec.buildRequests(duplicate.bids, duplicate); let data = parseQuery(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -412,7 +443,18 @@ describe('the rubicon adapter', function () { it('should correctly send hard floors when getFloor function is present and returns valid floor', function () { // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => getFloorResponse; + sinon.spy(bidderRequest.bids[0], 'getFloor'); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // make sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }) + ).to.be.true; + let data = parseQuery(request.data); expect(data.rp_hard_floor).to.be.undefined; @@ -446,35 +488,73 @@ describe('the rubicon adapter', function () { data = parseQuery(request.data); expect(data.rp_hard_floor).to.equal('1.23'); }); - it('should not send p_pos to AE if not params.position specified', function() { - var noposRequest = utils.deepClone(bidderRequest); - delete noposRequest.bids[0].params.position; - let [request] = spec.buildRequests(noposRequest.bids, noposRequest); - let data = parseQuery(request.data); + it('should send rp_maxbids to AE if rubicon multibid config exists', function () { + var multibidRequest = utils.deepClone(bidderRequest); + multibidRequest.bidLimit = 5; + + let [request] = spec.buildRequests(multibidRequest.bids, multibidRequest); + let data = parseQuery(request.data); + + expect(data['rp_maxbids']).to.equal('5'); + }); + + it('should not send p_pos to AE if not params.position specified', function () { + var noposRequest = utils.deepClone(bidderRequest); + delete noposRequest.bids[0].params.position; + + let [request] = spec.buildRequests(noposRequest.bids, noposRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + 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; - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + 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 not send p_pos to AE if not params.position is invalid', function() { - var badposRequest = utils.deepClone(bidderRequest); - badposRequest.bids[0].params.position = 'bad'; + 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(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + 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); + 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'; + + let [request] = spec.buildRequests(badposRequest.bids, badposRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); }); it('should correctly send p_pos in sra fashion', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: true}}); // first one is atf var sraPosRequest = utils.deepClone(bidderRequest); @@ -504,11 +584,11 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); - it('should not send x_source.pchain to AE if params.pchain is not specified', function() { - var noPchainRequest = utils.deepClone(bidderRequest); - delete noPchainRequest.bids[0].params.pchain; + it('should not send x_source.pchain to AE if params.pchain is not specified', function () { + var noPchainRequest = utils.deepClone(bidderRequest); + delete noPchainRequest.bids[0].params.pchain; - let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); + let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); expect(request.data).to.contain('&site_id=70608&'); expect(request.data).to.not.contain('x_source.pchain'); }); @@ -517,7 +597,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -532,7 +612,6 @@ describe('the rubicon adapter', function () { 'size_id': '15', 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': '0.01', 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, @@ -609,7 +688,7 @@ describe('the rubicon adapter', function () { expect(parseQuery(request.data).rf).to.equal('localhost'); delete bidderRequest.bids[0].params.referrer; - let refererInfo = { referer: 'https://www.prebid.org' }; + let refererInfo = {referer: 'https://www.prebid.org'}; bidderRequest = Object.assign({refererInfo}, bidderRequest); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); @@ -668,233 +747,6 @@ describe('the rubicon adapter', function () { expect(data['rp_floor']).to.equal('2'); }); - it('should send digitrust params', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let expectedQuery = { - 'dt.id': 'testId', - 'dt.keyv': 'testKeyV', - 'dt.pref': '0' - }; - - // test that all values above are both present and correct - Object.keys(expectedQuery).forEach(key => { - let value = expectedQuery[key]; - expect(data[key]).to.equal(value); - }); - - delete window.DigiTrust; - }); - - it('should not send digitrust params when DigiTrust not loaded', function () { - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - }); - - it('should not send digitrust params due to optout', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - delete window.DigiTrust; - }); - - it('should not send digitrust params due to failure', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - delete window.DigiTrust; - }); - - describe('digiTrustId config', function () { - beforeEach(function () { - window.DigiTrust = { - getUser: sandbox.spy() - }; - }); - - afterEach(function () { - delete window.DigiTrust; - }); - - it('should send digiTrustId config params', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - } - }; - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let expectedQuery = { - 'dt.id': 'testId', - 'dt.keyv': 'testKeyV' - }; - - // test that all values above are both present and correct - Object.keys(expectedQuery).forEach(key => { - let value = expectedQuery[key]; - expect(data[key]).to.equal(value); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params due to optout', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 'testKeyV' - } - } - } - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params due to failure', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - } - } - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params if they do not exist', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = {}; - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should have called DigiTrust.getUser() once - expect(window.DigiTrust.getUser.calledOnce).to.equal(true); - }); - }); - describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { createGdprBidderRequest(true); @@ -1017,22 +869,68 @@ describe('the rubicon adapter', function () { }); }); - it('should use first party data from getConfig over the bid params, if present', () => { - const context = { - keywords: ['e', 'f'], - rating: '4-star' + it('should merge first party data from getConfig with the bid params, if present', () => { + const site = { + keywords: 'e,f', + rating: '4-star', + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{ + 'name': 'www.dataprovider1.com', + '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': 6 }, + 'segment': [ + { 'id': '66' } + ] + } + ] + } }; const user = { - keywords: ['d'], + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'segtax': 4 }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', - geo: { country: 'ca' } + geo: {country: 'ca'}, + keywords: 'd', + ext: { + data: { + age: 40 + } + } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -1040,14 +938,17 @@ describe('the rubicon adapter', function () { }); const expectedQuery = { - 'kw': 'a,b,c,d,e,f', + 'kw': 'a,b,c,d', 'tg_v.ucat': 'new', 'tg_v.lastsearch': 'iphone', 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', + 'tg_v.age': '40', + 'tg_v.iab': '687,123', + 'tg_i.iab': '987,432,55,66', 'tg_v.yob': '1984', - 'tg_v.geo': '{"country":"ca"}', - 'tg_i.rating': '4-star', + 'tg_i.rating': '4-star,5-star', + 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1067,12 +968,7 @@ describe('the rubicon adapter', function () { it('should group all bid requests with the same site id', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: true}}); const expectedQuery = { 'account_id': '14062', @@ -1081,7 +977,6 @@ describe('the rubicon adapter', function () { 'size_id': '15', 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': '0.01', 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, @@ -1180,13 +1075,7 @@ describe('the rubicon adapter', function () { }); it('should not send more than 10 bids in a request (split into separate requests with <= 10 bids each)', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); - + config.setConfig({rubicon: {singleRequest: true}}); let serverRequests; let data; @@ -1228,12 +1117,7 @@ describe('the rubicon adapter', function () { }); it('should not group bid requests if singleRequest does not equal true', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': false - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: false}}); const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); @@ -1251,12 +1135,7 @@ describe('the rubicon adapter', function () { }); it('should not group video bid requests', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: true}}); const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); @@ -1300,33 +1179,39 @@ describe('the rubicon adapter', function () { }); }); - describe('user id config', function() { - it('should send tpid_tdid when userId defines tdid', function () { + describe('user id config', function () { + it('should send tpid_tdid when userIdAsEids contains unifiedId', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { tdid: 'abcd-efgh-ijkl-mnop-1234' }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = parseQuery(request.data); expect(data['tpid_tdid']).to.equal('abcd-efgh-ijkl-mnop-1234'); + expect(data['eid_adserver.org']).to.equal('abcd-efgh-ijkl-mnop-1234'); }); describe('LiveIntent support', function () { - it('should send tpid_liveintent.com when userId defines lipd', function () { + it('should send tpid_liveintent.com when userIdAsEids contains liveintentId', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { lipb: { - lipbid: '0000-1111-2222-3333' + lipbid: '0000-1111-2222-3333', + segments: ['segA', 'segB'] } }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = parseQuery(request.data); expect(data['tpid_liveintent.com']).to.equal('0000-1111-2222-3333'); + expect(data['eid_liveintent.com']).to.equal('0000-1111-2222-3333'); + expect(data['tg_v.LIseg']).to.equal('segA,segB'); }); - it('should send tg_v.LIseg when userId defines lipd.segments', function () { + it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { lipb: { @@ -1334,6 +1219,7 @@ describe('the rubicon adapter', function () { segments: ['segD', 'segE'] } }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); let [request] = spec.buildRequests([clonedBid], bidderRequest); const unescapedData = unescape(request.data); @@ -1343,49 +1229,237 @@ describe('the rubicon adapter', function () { }); describe('LiveRamp support', function () { - it('should send tpid_liveramp.com when userId defines idl_env', function () { + it('should send x_liverampidl when userIdAsEids contains liverampId', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { idl_env: '1111-2222-3333-4444' }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); let [request] = spec.buildRequests([clonedBid], bidderRequest); let data = parseQuery(request.data); - expect(data['tpid_liveramp.com']).to.equal('1111-2222-3333-4444'); + expect(data['x_liverampidl']).to.equal('1111-2222-3333-4444'); }); }); - }) + + describe('pubcid support', function () { + it('should send eid_pubcid.org when userIdAsEids contains pubcid', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + pubcid: '1111' + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_pubcid.org']).to.equal('1111^1'); + }); + }); + + describe('Criteo support', function () { + it('should send eid_criteo.com when userIdAsEids contains criteo', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + criteoId: '1111' + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_criteo.com']).to.equal('1111^1'); + }); + }); + + describe('pubProvidedId support', function () { + it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + pubProvidedId: [{ + source: 'example.com', + uids: [{ + id: '11111', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: '222222' + }] + }] + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['ppuid']).to.equal('11111'); + }); + }); + + describe('ID5 support', function () { + it('should send ID5 id when userIdAsEids contains ID5', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + id5id: { + uid: '11111', + ext: { + linkType: '22222' + } + } + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_id5-sync.com']).to.equal('11111^1^22222'); + }); + }); + + describe('UserID catchall support', function () { + it('should send user id with generic format', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js + clonedBid.userIdAsEids = [{ + source: 'catchall', + uids: [{ + id: '11111', + atype: 2 + }] + }] + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_catchall']).to.equal('11111^2'); + }); + }); + + describe('Config user.id support', function () { + it('should send ppuid when config defines user.id', function () { + config.setConfig({user: {id: '123'}}); + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + pubcid: '1111' + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['ppuid']).to.equal('123'); + }); + }); + }); describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.pbadslot'); + }); + + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + 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 () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + }); + }); + + describe('GAM ad unit', function () { + beforeEach(function () { + // enforce that the bid at 0 does not have a 'context' property + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; + } + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } + } } }; @@ -1396,10 +1470,14 @@ 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 \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } + } } } @@ -1411,10 +1489,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + 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 () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } + } } }; @@ -1439,11 +1521,11 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; - expect(post).to.have.property('imp') + expect(post).to.have.property('imp'); // .with.length.of(1); let imp = post.imp[0]; expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(300); + expect(imp.exp).to.equal(undefined); // now undefined expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); @@ -1462,20 +1544,42 @@ 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'); + // LiveIntent should exist expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.tpid).that.is.an('object'); - expect(post.user.ext.tpid.source).to.equal('liveintent.com'); - expect(post.user.ext.tpid.uid).to.equal('0000-1111-2222-3333'); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); + expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); + expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); + expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); + expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); // LiveRamp should exist 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.rp).that.is.an('object'); - expect(post.rp.target).that.is.an('object'); - expect(post.rp.target.LIseg).that.is.an('array'); - expect(post.rp.target.LIseg[0]).to.equal('segA'); - expect(post.rp.target.LIseg[1]).to.equal('segB'); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); + // UnifiedId should exist + 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[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[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[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[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'); expect(post).to.have.property('ext').that.is.an('object'); @@ -1491,12 +1595,23 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => getFloorResponse; + sinon.spy(bidderRequest.bids[0], 'getFloor'); + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // make sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: 'video', + size: [640, 480] + }) + ).to.be.true; + // not an object should work and not send expect(request.data.imp[0].bidfloor).to.be.undefined; @@ -1520,7 +1635,31 @@ describe('the rubicon adapter', function () { [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.imp[0].bidfloor).to.equal(1.23); }); - it('should add alias name to PBS Request', function() { + + it('should continue with auction and log error if getFloor throws one', function () { + createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => { + throw new Error('An exception!'); + }; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // log error called + expect(logErrorSpy.calledOnce).to.equal(true); + + // should have an imp + expect(request.data.imp).to.exist.and.to.be.a('array'); + expect(request.data.imp).to.have.lengthOf(1); + + // should be NO bidFloor + expect(request.data.imp[0].bidfloor).to.be.undefined; + }); + + it('should add alias name to PBS Request', function () { createVideoBidderRequest(); bidderRequest.bidderCode = 'superRubicon'; @@ -1536,7 +1675,93 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); - it('should send correct bidfloor to PBS', function() { + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); + + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); + + it('should pass client analytics to PBS endpoint if all modules included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); + + it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); + + it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; + + expect(payload.ext.prebid.analytics).to.be.undefined; + }); + + it('should send video exp param correctly when set', function () { + createVideoBidderRequest(); + config.setConfig({s2sConfig: {defaultTtl: 600}}); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); + + it('should not send video exp at all if not set in s2sConfig config', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + // should exp set to the right value according to config + let imp = post.imp[0]; + // bidderFactory stringifies request body before sending so removes undefined attributes: + expect(imp.exp).to.equal(undefined); + }); + + it('should send tmax as the bidderRequest timeout value', function () { + createVideoBidderRequest(); + bidderRequest.timeout = 3333; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + expect(post.tmax).to.equal(3333); + }); + + it('should send correct bidfloor to PBS', function () { createVideoBidderRequest(); bidderRequest.bids[0].params.floor = 0.1; @@ -1663,16 +1888,6 @@ describe('the rubicon adapter', function () { delete bidderRequest.bids[0].mediaTypes.video.protocols; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change maxduration to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.maxduration = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete maxduration, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.maxduration; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change linearity to an string, no good createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; @@ -1726,14 +1941,14 @@ describe('the rubicon adapter', function () { let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); bidderRequest.mediaTypes.video.context = 'instream'; requests = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { @@ -1752,7 +1967,7 @@ describe('the rubicon adapter', function () { let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); it('should include coppa flag in video bid request', () => { @@ -1776,39 +1991,58 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - keywords: ['e', 'f'], - rating: '4-star' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] + }, + keywords: 'e,f', + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - keywords: ['d'], + ext: { + data: { + age: 31 + } + }, + keywords: 'd', gender: 'M', yob: '1984', - geo: { country: 'ca' } + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; return utils.deepAccess(config, key); }); - const expected = [{ - bidders: [ 'rubicon' ], - config: { - fpd: { - site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), - user: Object.assign({}, bidderRequest.bids[0].params.visitor, user) - } - } - }]; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected); + + const expected = { + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), + }; + + delete request.data.site.page; + delete request.data.site.content.language; + + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1827,56 +2061,113 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); - it('should use the integration type provided in the config instead of the default', () => { + it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - sandbox.stub(config, 'getConfig').callsFake(function (key) { - const config = { - 'rubicon.int_type': 'testType' - }; - return config[key]; - }); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } + } + } + }; + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); + }); + + it('should use the integration type provided in the config instead of the default', () => { + createVideoBidderRequest(); + config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); }); - }); - it('should include pbAdSlot in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' - } - }; + it('should pass the user.id provided in the config', function () { + config.setConfig({user: {id: '123'}}); + createVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + expect(post).to.have.property('imp') + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.context).to.equal('instream'); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.rubicon.video.size_id).to.equal(201); + expect(imp.ext.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language + expect(post.site.content.language).to.equal('en'); + 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); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + // Config user.id + expect(post.user.id).to.equal('123'); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }) }); describe('combineSlotUrlParams', function () { it('should combine an array of slot url params', function () { expect(spec.combineSlotUrlParams([])).to.deep.equal({}); - expect(spec.combineSlotUrlParams([{p1: 'foo', p2: 'test', p3: ''}])).to.deep.equal({p1: 'foo', p2: 'test', p3: ''}); + expect(spec.combineSlotUrlParams([{p1: 'foo', p2: 'test', p3: ''}])).to.deep.equal({ + p1: 'foo', + p2: 'test', + p3: '' + }); expect(spec.combineSlotUrlParams([{}, {p1: 'foo', p2: 'test'}])).to.deep.equal({p1: ';foo', p2: ';test'}); @@ -1907,7 +2198,6 @@ describe('the rubicon adapter', function () { 'size_id': 15, 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': 0.01, 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', @@ -1939,7 +2229,7 @@ describe('the rubicon adapter', function () { it('should not fail if keywords param is not an array', function () { bidderRequest.bids[0].params.keywords = 'a,b,c'; const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); - expect(slotParams.kw).to.equal(''); + expect(slotParams.kw).to.equal('a,b,c'); }); }); @@ -2018,6 +2308,7 @@ describe('the rubicon adapter', function () { 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', 'size_id': '15', 'ad_id': '6', + 'adomain': ['test.com'], 'advertiser': 7, 'network': 8, 'creative_id': 'crid-9', @@ -2039,6 +2330,7 @@ describe('the rubicon adapter', function () { 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', 'size_id': '43', 'ad_id': '7', + 'adomain': ['test.com'], 'advertiser': 7, 'network': 8, 'creative_id': 'crid-9', @@ -2073,6 +2365,8 @@ describe('the rubicon adapter', function () { expect(bids[0].rubicon.networkId).to.equal(8); expect(bids[0].creativeId).to.equal('crid-9'); expect(bids[0].currency).to.equal('USD'); + expect(bids[0].meta.mediaType).to.equal('banner'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); expect(bids[0].ad).to.contain(`alert('foo')`) .and.to.contain(``) .and.to.contain(`
`); @@ -2357,6 +2651,146 @@ describe('the rubicon adapter', function () { expect(bids[0].cpm).to.be.equal(0); }); + it('should create bids with matching requestIds if imp id matches', function () { + let bidRequests = [{ + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '557ba307cef098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }, { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '456gt123jkl098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }]; + + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 1.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + + config.setConfig({ multibid: [{bidder: 'rubicon', maxbids: 2, targetbiddercodeprefix: 'rubi'}] }); + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidRequests + }); + + expect(bids).to.be.lengthOf(3); + expect(bids[0].requestId).to.not.equal(bids[1].requestId); + expect(bids[1].requestId).to.equal(bids[2].requestId); + }); + it('should handle an error with no ads returned', function () { let response = { 'status': 'ok', @@ -2432,7 +2866,7 @@ describe('the rubicon adapter', function () { }] }; - let bids = spec.interpretResponse({ body: response }, { + let bids = spec.interpretResponse({body: response}, { bidRequest: [utils.deepClone(bidderRequest.bids[0])] }); @@ -2443,7 +2877,7 @@ describe('the rubicon adapter', function () { describe('singleRequest enabled', function () { it('handles bidRequest of type Array and returns associated adUnits', function () { const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; + overrideMap[0] = {impression_id: '1'}; const stubAds = []; for (let i = 0; i < 10; i++) { @@ -2465,7 +2899,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); expect(bids).to.be.a('array').with.lengthOf(10); bids.forEach((bid) => { @@ -2498,7 +2933,7 @@ describe('the rubicon adapter', function () { it('handles incorrect adUnits length by returning all bids with matching ads', function () { const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; + overrideMap[0] = {impression_id: '1'}; const stubAds = []; for (let i = 0; i < 6; i++) { @@ -2520,7 +2955,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); // no bids expected because response didn't match requested bid number expect(bids).to.be.a('array').with.lengthOf(6); @@ -2531,11 +2967,11 @@ describe('the rubicon adapter', function () { // Create overrides to break associations between bids and ads // Each override should cause one less bid to be returned by interpretResponse const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; - overrideMap[2] = { status: 'error' }; - overrideMap[4] = { status: 'error' }; - overrideMap[7] = { status: 'error' }; - overrideMap[8] = { status: 'error' }; + overrideMap[0] = {impression_id: '1'}; + overrideMap[2] = {status: 'error'}; + overrideMap[4] = {status: 'error'}; + overrideMap[7] = {status: 'error'}; + overrideMap[8] = {status: 'error'}; for (let i = 0; i < 10; i++) { stubAds.push(createResponseAdByIndex(i, sizeMap[i].sizeId, overrideMap)); @@ -2556,7 +2992,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); expect(bids).to.be.a('array').with.lengthOf(6); bids.forEach((bid) => { @@ -2603,13 +3040,15 @@ describe('the rubicon adapter', function () { bid: [{ id: '0', impid: 'instream_video1', + adomain: ['test.com'], price: 2, crid: '4259970', ext: { bidder: { rp: { mime: 'application/javascript', - size_id: 201 + size_id: 201, + advid: 12345 } }, prebid: { @@ -2638,6 +3077,9 @@ describe('the rubicon adapter', function () { expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].currency).to.equal('USD'); expect(bids[0].width).to.equal(640); @@ -2645,14 +3087,156 @@ describe('the rubicon adapter', function () { }); }); + describe('for outstream video', function () { + const sandbox = sinon.createSandbox(); + beforeEach(function () { + createVideoBidderRequestOutstream(); + config.setConfig({rubicon: { + rendererConfig: { + align: 'left', + closeButton: true + }, + rendererUrl: 'https://example.test/renderer.js' + }}); + window.MagniteApex = { + renderAd: function() { + return null; + } + } + }); + + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); + + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: 'outstream_video1', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + } + }], + group: 0, + seat: 'rubicon' + }], + }; + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(320); + // check custom renderer + expect(typeof bids[0].renderer).to.equal('object'); + expect(bids[0].renderer.getConfig()).to.deep.equal({ + align: 'left', + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: 'outstream_video1', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + }, + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' + }], + }; + + sinon.spy(window.MagniteApex, 'renderAd'); + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 320, + label: undefined, + placement: { + align: 'left', + attachTo: '#outstream_video1_placement', + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); + }); + }); + describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { - sandbox.stub(config, 'getConfig').callsFake(function (key) { - const config = { - 'rubicon.int_type': 'testType' - }; - return config[key]; - }); + config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).tk_flint).to.equal('testType_v$prebid.version$'); }); @@ -2687,7 +3271,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { gdprApplies: true, consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo` @@ -2695,7 +3279,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { gdprApplies: false, consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr=0&gdpr_consent=foo` @@ -2703,7 +3287,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo` @@ -2711,13 +3295,13 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {})).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, {})).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` }); }); it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 0 })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2725,7 +3309,7 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: null })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2733,7 +3317,7 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: {} })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2741,19 +3325,19 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined)).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined)).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` }); }); it('should pass us_privacy if uspConsent is defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined, '1NYN')).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?us_privacy=1NYN` }); }); it('should pass us_privacy after gdpr if both are present', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 'foo' }, '1NYN')).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` @@ -2761,8 +3345,8 @@ describe('the rubicon adapter', function () { }); }); - describe('get price granularity', function() { - it('should return correct buckets for all price granularity values', function() { + describe('get price granularity', function () { + it('should return correct buckets for all price granularity values', function () { const CUSTOM_PRICE_BUCKET_ITEM = {max: 5, increment: 0.5}; const mockConfig = { @@ -2795,7 +3379,7 @@ describe('the rubicon adapter', function () { }); }); - describe('Supply Chain Support', function() { + describe('Supply Chain Support', function () { const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; let bidRequests; let schainConfig; @@ -2888,4 +3472,51 @@ describe('the rubicon adapter', function () { expect(request[0].data.source.ext.schain).to.deep.equal(schain); }); }); + + describe('configurable settings', function() { + afterEach(() => { + config.setConfig({ + rubicon: { + bannerHost: 'rubicon', + videoHost: 'prebid-server', + syncHost: 'eus', + returnVast: false + } + }); + config.resetConfig(); + }); + + beforeEach(function () { + resetUserSync(); + }); + + it('should update fastlane endpoint if', function () { + config.setConfig({ + rubicon: { + bannerHost: 'fastlane-qa', + videoHost: 'prebid-server-qa', + syncHost: 'eus-qa', + returnVast: true + } + }); + + // banner + let [bannerRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(bannerRequest.url).to.equal('https://fastlane-qa.rubiconproject.com/a/api/fastlane.json'); + + // video and returnVast + createVideoBidderRequest(); + let [videoRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = videoRequest.data; + expect(videoRequest.url).to.equal('https://prebid-server-qa.rubiconproject.com/openrtb2/auction'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(true); + + // user sync + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.deep.equal({type: 'iframe', url: 'https://eus-qa.rubiconproject.com/usync.html'}); + }); + }); }); diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index 5c7f3004dee..273f6747e52 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,5 +1,4 @@ import s2sTesting from 'modules/s2sTesting.js'; -import { config } from 'src/config.js'; var expect = require('chai').expect; @@ -78,26 +77,16 @@ describe('s2sTesting', function () { beforeEach(function () { // set random number for testing s2sTesting.globalRand = 0.7; - }); - - it('does not work if testing is "false"', function () { - config.setConfig({s2sConfig: { - bidders: ['rubicon'], - testing: false, - bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); - expect(s2sTesting.getSourceBidderMap()).to.eql({ - server: [], - client: [] - }); + s2sTesting.bidSource = {}; }); it('sets one client bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); + }; + + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: [], client: ['rubicon'] @@ -105,11 +94,11 @@ describe('s2sTesting', function () { }); it('sets one server bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 4, client: 1}}} - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -117,10 +106,10 @@ describe('s2sTesting', function () { }); it('defaults to server', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -128,13 +117,14 @@ describe('s2sTesting', function () { }); it('sets two bidders', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 3, client: 1}}, appnexus: {bidSource: {server: 1, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(); expect(serverClientBidders.server).to.eql(['rubicon']); expect(serverClientBidders.client).to.have.members(['appnexus']); @@ -143,33 +133,37 @@ describe('s2sTesting', function () { it('sends both bidders to same source when weights are the same', function () { s2sTesting.globalRand = 0.01; - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 1, client: 99}}, appnexus: {bidSource: {server: 1, client: 99}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); - config.setConfig({s2sConfig: { + const s2sConfig2 = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 99, client: 1}}, appnexus: {bidSource: {server: 99, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig2); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon', 'appnexus'], client: [] @@ -186,11 +180,12 @@ describe('s2sTesting', function () { }); describe('setting source through adUnits', function () { + const s2sConfig3 = {testing: true}; + beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; + s2sTesting.bidSource = {}; }); it('sets one bidder source from one adUnit', function () { @@ -199,7 +194,8 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 4, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: ['rubicon'], client: [] }); @@ -212,7 +208,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 1, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -227,7 +223,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -243,7 +239,7 @@ describe('s2sTesting', function () { {bidder: 'appnexus', bidSource: {server: 3, client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.eql(['appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon']); // should have saved the source on the bid @@ -264,7 +260,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['rubicon']); expect(serverClientBidders.server).to.not.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus', 'bidder3']); @@ -287,7 +283,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', calcSource: 'server', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.server).to.not.have.members(['rubicon']); @@ -305,8 +301,6 @@ describe('s2sTesting', function () { describe('setting source through s2sconfig and adUnits', function () { beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; }); @@ -321,15 +315,15 @@ describe('s2sTesting', function () { ]; // set rubicon: client and appnexus: server - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], testing: true, bidderControl: { rubicon: {bidSource: {server: 2, client: 1}}, appnexus: {bidSource: {server: 1}} } - }}); - + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); expect(serverClientBidders.server).to.have.members(['rubicon', 'appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus']); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js deleted file mode 100755 index e6f96c92fd9..00000000000 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ /dev/null @@ -1,186 +0,0 @@ -// jshint esversion: 6, es3: false, node: true -import {assert, expect} from 'chai'; -import {spec} from 'modules/seedingAllianceBidAdapter.js'; -import { NATIVE } from 'src/mediaTypes.js'; -import { config } from 'src/config.js'; - -describe('SeedingAlliance adapter', function () { - let serverResponse, bidRequest, bidResponses; - let bid = { - 'bidder': 'seedingAlliance', - 'params': { - 'adUnitId': '1hq8' - } - }; - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); - }); - - it('should return false when AdUnitId is not set', function () { - delete bid.params.adUnitId; - assert.isFalse(spec.isBidRequestValid(bid)); - }); - }); - - describe('buildRequests', function () { - it('should send request with correct structure', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {} - }]; - - let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); - - assert.equal(request.method, 'POST'); - assert.ok(request.data); - }); - - it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user'.split(','); - let validBidRequests = [{ - bidId: 'bidId', - params: {} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - let data = Object.keys(request); - - assert.deepEqual(keys, data); - }); - - it('Verify the auction ID', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - auctionId: 'auctionId' - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' }, auctionId: validBidRequests[0].auctionId }).data); - - assert.equal(request.id, validBidRequests[0].auctionId); - }); - - it('Verify the device', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - - assert.equal(request.device.ua, navigator.userAgent); - }); - - it('Verify native asset ids', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - nativeParams: { - body: { - required: true, - len: 350 - }, - image: { - required: true - }, - title: { - required: true - }, - sponsoredBy: { - required: true - }, - cta: { - required: true - }, - icon: { - required: true - } - } - }]; - - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; - - assert.equal(assets[0].id, 1); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 0); - assert.equal(assets[3].id, 2); - assert.equal(assets[4].id, 4); - assert.equal(assets[5].id, 5); - }); - }); - - describe('interpretResponse', function () { - const goodResponse = { - body: { - cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', - seatbid: [ - { - seat: 'seedingAlliance', - bid: [{ - adm: { - native: { - assets: [ - {id: 0, title: {text: 'this is a title'}} - ], - imptrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], - link: { - clicktrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], - url: 'https://domain.for/ad/' - } - } - }, - impid: 1, - price: 0.55 - }] - } - ] - } - }; - const badResponse = { body: { - cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', - seatbid: [] - }}; - - const bidRequest = { - data: {}, - bids: [{ bidId: 'bidId1' }] - }; - - it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); - assert.equal(result.length, 0); - - delete badResponse.body - - const result1 = spec.interpretResponse(badResponse, bidRequest); - assert.equal(result.length, 0); - }); - - it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(result[0].cpm, bid.price); - assert.deepEqual(result[0].creativeId, bid.crid); - assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); - }); - - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - const regExpPrice = new RegExp('price=' + bid.price); - - result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); - }); - - result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); - }); - }); - }); -}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 5c8c58196f7..3aa378379dd 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,5 +1,9 @@ import { expect } from 'chai' import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' +import * as utils from 'src/utils.js' + +const PUBLISHER_ID = '0000-0000-01' +const ADUNIT_ID = '000000' function getSlotConfigs(mediaTypes, params) { return { @@ -12,14 +16,21 @@ 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' } } +function createVideoSlotConfig(mediaType) { + return getSlotConfigs(mediaType, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement: 'video' + }) +} + describe('Seedtag Adapter', function() { describe('isBidRequestValid method', function() { - const PUBLISHER_ID = '0000-0000-01' - const ADUNIT_ID = '000000' describe('returns true', function() { describe('when banner slot config has all mandatory params', () => { describe('and placement has the correct value', function() { @@ -33,7 +44,7 @@ describe('Seedtag Adapter', function() { } ) } - const placements = ['banner', 'video', 'inImage', 'inScreen'] + const placements = ['banner', 'video', 'inImage', 'inScreen', 'inArticle'] placements.forEach(placement => { it('should be ' + placement, function() { const isBidRequestValid = spec.isBidRequestValid( @@ -44,8 +55,8 @@ describe('Seedtag Adapter', function() { }) }) }) - describe('when video slot has all mandatory params.', function() { - it('should return true, when video mediatype object are correct.', function() { + describe('when video slot has all mandatory params', function() { + it('should return true, when video context is instream', function () { const slotConfig = getSlotConfigs( { video: { @@ -62,17 +73,35 @@ 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() { describe('when params are not correct', function() { - function createSlotconfig(params) { + function createSlotConfig(params) { return getSlotConfigs({ banner: {} }, params) } it('does not have the PublisherToken.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - adUnitId: '000000', + createSlotConfig({ + adUnitId: ADUNIT_ID, placement: 'banner' }) ) @@ -80,8 +109,8 @@ describe('Seedtag Adapter', function() { }) it('does not have the AdUnitId.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', + createSlotConfig({ + publisherId: PUBLISHER_ID, placement: 'banner' }) ) @@ -89,25 +118,25 @@ describe('Seedtag Adapter', function() { }) it('does not have the placement.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', - adUnitId: '000000' + createSlotConfig({ + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID }) ) expect(isBidRequestValid).to.equal(false) }) it('does not have a the correct placement.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', - adUnitId: '000000', + createSlotConfig({ + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, placement: 'another_thing' }) ) expect(isBidRequestValid).to.equal(false) }) }) - describe('when video mediaType object is not correct.', function() { + describe('when video mediaType object is not correct', function() { function createVideoSlotconfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, @@ -117,26 +146,40 @@ describe('Seedtag Adapter', function() { } it('is a void object', function() { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ video: {} }) + createVideoSlotConfig({ video: {} }) ) expect(isBidRequestValid).to.equal(false) }) it('does not have playerSize.', function() { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ video: { context: 'instream' } }) + createVideoSlotConfig({ video: { context: 'instream' } }) ) expect(isBidRequestValid).to.equal(false) }) - it('is not instream ', function() { + it('is outstream ', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ + createVideoSlotConfig({ video: { context: 'outstream', playerSize: [[600, 200]] } }) ) - 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() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotConfig({ banner: {}, video: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('when video is the first slot', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotConfig({ video: {}, banner: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) }) }) }) @@ -148,8 +191,8 @@ describe('Seedtag Adapter', function() { timeout: 1000 } const mandatoryParams = { - publisherId: '0000-0000-01', - adUnitId: '000000', + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, placement: 'banner' } const inStreamParams = Object.assign({}, mandatoryParams, { @@ -176,6 +219,8 @@ describe('Seedtag Adapter', function() { expect(data.url).to.equal('referer') 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() { @@ -248,6 +293,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] @@ -264,6 +310,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) }) }) }) @@ -278,7 +325,7 @@ describe('Seedtag Adapter', function() { expect(typeof bids).to.equal('object') expect(bids.length).to.equal(0) }) - it('should return a void array, when the server response have not got bids.', function() { + it('should return a void array, when the server response have no bids.', function() { const request = { data: JSON.stringify({}) } const serverResponse = { body: { bids: [] } } const bids = spec.interpretResponse(serverResponse, request) @@ -300,7 +347,9 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'display', - ttl: 360 + ttl: 360, + nurl: 'testurl.com/nurl', + adomain: ['advertiserdomain.com'] } ], cookieSync: { url: '' } @@ -315,6 +364,8 @@ describe('Seedtag Adapter', function() { expect(bids[0].currency).to.equal('USD') 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() { @@ -331,7 +382,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'video', - ttl: 360 + ttl: 360, + nurl: undefined } ], cookieSync: { url: '' } @@ -346,6 +398,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([]) }) }) }) @@ -381,6 +434,14 @@ describe('Seedtag Adapter', function() { }) describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + it('should return the correct endpoint', function () { const params = { publisherId: '0000', adUnitId: '11111' } const timeoutData = [{ params: [ params ] }]; @@ -392,5 +453,44 @@ describe('Seedtag Adapter', function() { params.adUnitId ) }) + + it('should set the timeout pixel', function() { + const params = { publisherId: '0000', adUnitId: '11111' } + const timeoutData = [{ params: [ params ] }]; + spec.onTimeout(timeoutData) + expect(utils.triggerPixel.calledWith('https://s.seedtag.com/se/hb/timeout?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId)).to.equal(true); + }) + }) + + describe('onBidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + + describe('without nurl', function() { + const bid = {} + + it('does not create pixel ', function() { + spec.onBidWon(bid) + expect(utils.triggerPixel.called).to.equal(false); + }) + }) + + describe('with nurl', function () { + const nurl = 'http://seedtag_domain/won' + const bid = { nurl } + + it('creates nurl pixel if bid nurl', function() { + spec.onBidWon({ nurl }) + expect(utils.triggerPixel.calledWith(nurl)).to.equal(true); + }) + }) }) }) 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/shareUserIds_spec.js b/test/spec/modules/shareUserIds_spec.js index 451892919cb..67e39533fc7 100644 --- a/test/spec/modules/shareUserIds_spec.js +++ b/test/spec/modules/shareUserIds_spec.js @@ -50,6 +50,16 @@ describe('#userIdTargeting', function() { pubads.setTargeting('test', ['TEST']); config.GAM_KEYS.tdid = ''; userIdTargeting(userIds, config); + expect(pubads.getTargeting('tdid')).to.be.an('array').that.is.empty; expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); }); + + it('User Id Targeting is added to googletag queue when GPT is not ready', function() { + let pubads = window.googletag.pubads; + delete window.googletag.pubads; + userIdTargeting(userIds, config); + window.googletag.pubads = pubads; + window.googletag.cmd.map(command => command()); + expect(window.googletag.pubads().getTargeting('TD_ID')).to.deep.equal(['my-tdid']); + }); }); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js new file mode 100644 index 00000000000..534d0b3f381 --- /dev/null +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -0,0 +1,94 @@ +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 UUID = '15fde1dc-1861-4894-afdf-b757272f3568'; + + before(function () { + sinon.stub(utils, 'generateUUID').returns(UUID); + sinon.stub(utils, 'logInfo'); + }); + + after(function () { + utils.generateUUID.restore(); + utils.logInfo.restore(); + }); + describe('SharedId System getId()', 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); + coppaDataHandlerDataStub.returns(''); + callbackSpy.resetHistory(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should call UUID', function () { + let config = { + storage: { + type: 'cookie', + name: '_pubcid', + expires: 10 + } + }; + + let submoduleCallback = sharedIdSystemSubmodule.getId(config, undefined).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(UUID); + }); + it('should log message if coppa is set', function () { + coppaDataHandlerDataStub.returns('true'); + sharedIdSystemSubmodule.getId({}); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + }); + }); + 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 log message if coppa is set', function () { + coppaDataHandlerDataStub.returns('true'); + sharedIdSystemSubmodule.extendId({}, undefined, 'TestId'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + }); + }); +}); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 92f9fd11eeb..122c6cdbb39 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,497 +1,544 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.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' } - }, - { - 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] - }, - }, -]; - -const prebidRequests = [ - { - method: 'GET', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: false, - sizes: [] - } - }, - { - method: 'GET', - 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: 'GET', - 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: 'GET', - url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', - data: { - bidId: 'bidId', - placement_key: 'pKey' - }, - strData: { - skipIframeBusting: false, - sizes: [[0, 0]] - } - }, - { - method: 'GET', - 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 windowSpy, windowTopSpy; - - beforeEach(function() { - windowSpy = sinon.spy(window.document, 'getElementsByTagName'); - windowTopSpy = sinon.spy(window.top.document, 'getElementsByTagName'); - }); - - afterEach(function() { - windowSpy.restore(); - windowTopSpy.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; - }); +describe('sharethrough adapter spec', function() { + let protocolStub; + let inIframeStub; - it('appends sfp.js to the safeframe', function () { - sharethroughInternal.handleIframe(); - expect(windowSpy.calledOnce).to.be.true; - }); - - it('does not append anything if sfp.js is already loaded in the safeframe', function () { - window.STR = { Tag: true }; - sharethroughInternal.handleIframe(); - expect(windowSpy.notCalled).to.be.true; - expect(windowTopSpy.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(windowSpy.calledOnce).to.be.true; - expect(windowTopSpy.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(windowSpy.calledOnce).to.be.true; - expect(windowTopSpy.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('GET'); + 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, + placement: 1, + 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 builtRequest = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtRequest.method).to.equal('POST'); + expect(builtRequest.url).not.to.be.undefined; + expect(builtRequest.options).to.be.undefined; + expect(builtRequest.bidderRequest).to.deep.equal(bidderRequest); + + 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(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; + } - setUserAgent('iPhone Version/11'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.true; + expect(openRtbReq.device.ua).to.equal(navigator.userAgent); + expect(openRtbReq.regs.coppa).to.equal(1); - setUserAgent('iPhone CriOS/60'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.true; + 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); - setUserAgent('Android Chrome/50'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; + expect(openRtbReq.bcat).to.deep.equal(bidRequests[0].params.bcat); + expect(openRtbReq.badv).to.deep.equal(bidRequests[0].params.badv); - setUserAgent('Android Chrome'); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; + expect(openRtbReq.imp).to.have.length(2); - setUserAgent(undefined); - builtBidRequests = spec.buildRequests(bidRequests); - expect(builtBidRequests[0].data.instant_play_capable).to.be.false; - }); + expect(openRtbReq.imp[0].id).to.equal('bidId1'); + expect(openRtbReq.imp[0].tagid).to.equal('aaaa1111'); + expect(openRtbReq.imp[0].secure).to.equal(1); + expect(openRtbReq.imp[0].bidfloor).to.equal(42); - 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() - }); + expect(openRtbReq.imp[1].id).to.equal('bidId2'); + expect(openRtbReq.imp[1].tagid).to.equal('bbbb2222'); + expect(openRtbReq.imp[1].secure).to.equal(1); + expect(openRtbReq.imp[1].bidfloor).to.equal(42); + }); - 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() - }); + it('should have empty eid array if no id is provided', () => { + const openRtbReq = spec.buildRequests([bidRequests[1]], bidderRequest).data; - 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() - }); + expect(openRtbReq.user.ext.eids).to.deep.equal([]); + }); + }); - 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); - }); + describe('regulation', () => { + describe('gdpr', () => { + it('should populate request accordingly when gdpr applies', () => { + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent', + }; - 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'); - }); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; - 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') - }); + expect(openRtbReq.regs.ext.gdpr).to.equal(1); + expect(openRtbReq.user.ext.consent).to.equal('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'); - }); + it('should populate request accordingly when gdpr explicitly does not apply', () => { + bidderRequest.gdprConsent = { + gdprApplies: false, + }; - 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]] - } + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; + + expect(openRtbReq.regs.ext.gdpr).to.equal(0); + expect(openRtbReq.user.ext.consent).to.be.undefined; + }); + }); + + describe('US privacy', () => { + it('should populate request accordingly when us privacy applies', () => { + bidderRequest.uspConsent = 'consent'; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; + + expect(openRtbReq.regs.ext.us_privacy).to.equal('consent'); + }); + }); + + describe('coppa', () => { + it('should populate request accordingly when coppa does not apply', () => { + config.setConfig({ coppa: false }); + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; + + expect(openRtbReq.regs.coppa).to.equal(0); + }); + }); }); - }); - 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 - } - ] - }; + describe('universal id', () => { + it('should include gpid when universal id is provided', () => { + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; - const builtBidRequest = spec.buildRequests([bidRequest])[0]; - expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); - }); + expect(openRtbReq.imp[0].ext.gpid).to.equal('universal-id'); + expect(openRtbReq.imp[1].ext).to.be.undefined; + }); + }); - 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'); - }); + describe('secure flag', () => { + it('should be positive when protocol is https', () => { + protocolStub.returns('https'); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; - 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); - }); + expect(openRtbReq.imp[0].secure).to.equal(1); + expect(openRtbReq.imp[1].secure).to.equal(1); + }); - 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 be negative when protocol is http', () => { + protocolStub.returns('http'); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; - 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, + expect(openRtbReq.imp[0].secure).to.equal(0); + expect(openRtbReq.imp[1].secure).to.equal(0); }); - }); - 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, + it('should be positive when protocol is neither http nor https', () => { + protocolStub.returns('about'); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + const openRtbReq = builtRequest.data; + + expect(openRtbReq.imp[0].secure).to.equal(1); + expect(openRtbReq.imp[1].secure).to.equal(1); }); - }); + }); - 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('banner imp', () => { + it('should generate open rtb banner imp', () => { + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + + 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 }]); }); - }); - 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, + it('should default to pos 0 if not provided', () => { + delete bidRequests[0].mediaTypes; + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + + const bannerImp = builtRequest.data.imp[0].banner; + expect(bannerImp.pos).to.equal(0); }); - }); + }); - 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, + describe('video imp', () => { + it('should generate open rtb video imp', () => { + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); + + const videoImp = builtRequest.data.imp[1].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('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 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); + + const videoImp = builtRequest.data.imp[1].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.be.undefined; + expect(videoImp.delivery).to.be.undefined; + expect(videoImp.companiontype).to.be.undefined; + expect(videoImp.companionad).to.be.undefined; + }); - 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; - }); + it('should not return a video impression if context is outstream', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + const builtRequest = spec.buildRequests(bidRequests, bidderRequest); - 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; + const videoImp = builtRequest.data.imp[1]; + expect(videoImp).to.be.undefined; + }); + }); }); - it('correctly generates ad markup when skipIframeBusting is false', function () { - const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[0])[0].ad; - let resp = null; + describe('interpretResponse', function() { + let request; + let response; + + beforeEach(() => { + request = spec.buildRequests(bidRequests, bidderRequest); + 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', + }], + }], + }, + }; + }); - 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('banner', () => { + 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; + }); + }); - // insert functionality to autodetect whether or not in safeframe, and handle JS insertion - expect(adMarkup).to.match(/isLockedInFrame/); - expect(adMarkup).to.match(/handleIframe/); + describe('video', () => { + it('should return a video bid', () => { + const resp = spec.interpretResponse(response, request); + + const bannerBid = resp[1]; + 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', - 'params': { - 'placementId': 83 - }, - 'adUnitCode': 'adunit-code', - '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 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 = { - 'auctionId': '4e156668c977d7', - 'bidderRequestId': 'b41642f1aee381', - 'timeout': 3000, - 'gdprConsent': { - 'consentString': consentString, - 'gdprApplies': true, - 'vendorData': { - 'hasGlobalConsent': false - } - } - }; - - 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 () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequest = { - refererInfo: { - referer: 'https://example.com/page.html', - reachedTop: true, - numIframes: 2 - } - } - 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': { - 'responses': [{ - 'ad': AD_SCRIPT, - 'cpm': 0.5, - 'currency': 'USD', - 'height': 250, - 'netRevenue': true, - 'requestId': '3ede2a3fa0db94', - 'ttl': 360, - 'width': 300, - 'creativeId': 'er2ee', - 'transactionId': 'deadb33f', - 'winUrl': 'https://sb.freeskreen.com/win' - }] - } - }; - - it('should get the correct number of sync urls', () => { - 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); - expect(urls.length).to.equal(0); - }); - }); - - describe('interpretResponse', function() { - let bids = { - 'body': { - 'responses': [{ - 'ad': AD_SCRIPT, - 'cpm': 0.5, - 'currency': 'USD', - 'height': 250, - 'netRevenue': true, - 'requestId': '3ede2a3fa0db94', - 'ttl': 360, - 'width': 300, - 'creativeId': 'er2ee', - 'transactionId': 'deadb33f', - 'winUrl': 'https://sb.freeskreen.com/win' - }] - } - }; - - it('should get correct bid response', function() { - let expectedResponse = [{ - 'cpm': 0.5, - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360, - 'ad': AD_SCRIPT, - 'requestId': '3ede2a3fa0db94', - 'creativeId': 'er2ee', - 'transactionId': 'deadb33f', - 'winUrl': 'https://sb.freeskreen.com/win' - }]; - - 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 new file mode 100644 index 00000000000..faa288306a5 --- /dev/null +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -0,0 +1,1173 @@ +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 ADTYPE_IMG = 'Img'; +const ADTYPE_RICHMEDIA = 'Richmedia'; +const ADTYPE_VIDEO = 'Video'; + +const REFERRER = 'http://example.com/page.html' +const CONSENT_STRING = 'HFIDUYFIUYIUYWIPOI87392DSU' +const AUCTION_ID = '6653'; + +const defaultBidderRequest = { + gdprConsent: { + consentString: CONSENT_STRING, + gdprApplies: true + }, + uspConsent: 'uspConsentString', + refererInfo: { + referer: REFERRER, + }, + timeout: 1200, + auctionId: AUCTION_ID +}; + +const BANNER_PREBID_MEDIATYPE = { + sizes: [[300, 50]] +} + +const singleBannerBidRequest = { + 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 +}; + +const extractPayloadOfFirstAndOnlyRequest = (reqs) => { + expect(reqs).to.have.length(1); + return JSON.parse(reqs[0].data); +} + +describe('smaatoBidAdapterTest', () => { + describe('isBidRequestValid', () => { + 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; + }); + + it('is invalid, when publisherId is present but of wrong type', () => { + expect(spec.isBidRequestValid({params: {publisherId: 123}})).to.be.false; + }); + + 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; + }); + }); + + 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; + }); + }); + }); + + describe('buildRequests', () => { + const BANNER_OPENRTB_IMP = { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + } + + describe('common', () => { + const MINIMAL_BIDDER_REQUEST = { + refererInfo: { + referer: REFERRER, + } + }; + + it('auction type is 1 (first price auction)', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.at).to.be.equal(1); + }) + + 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 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); + + 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; + }); + }); + + 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); + }); + }); + + 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('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 + }; + + const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); + + 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, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: false + } + }, + 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 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; + + 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; + }); + }); + }); + }); + + 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; + }); + }); + + 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('interpretResponse', () => { + 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; + }); + + 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; + }); + }); + + 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); + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return ADTYPE_IMG; + } + if (header === 'X-SMT-Expires') { + return 2000 + (400 * 1000); + } + }; + + const bids = spec.interpretResponse(resp, buildBidRequest()); + + expect(bids[0].ttl).to.equal(400); + + clock.restore(); + }); + + it('uses net revenue flag send from server', () => { + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + resp.body.seatbid[0].bid[0].ext = {net: false}; + + 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 2faad47aca8..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 () { @@ -71,7 +60,7 @@ describe('Smart bid adapter tests', function () { britepoolid: '1111', criteoId: '1111', digitrustid: { data: { id: 'DTID', keyv: 4, privacy: { optout: false }, producer: 'ABC', version: 2 } }, - id5id: '1111', + id5id: { uid: '1111' }, idl_env: '1111', lipbid: '1111', parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', @@ -115,7 +104,57 @@ describe('Smart bid adapter tests', function () { ttl: 300, adUrl: 'http://awesome.fake.url', ad: '< --- awesome script --- >', - cSyncUrl: 'http://awesome.fake.csync.url' + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false + } + }; + + var BID_RESPONSE_IS_NO_AD = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: true + } + }; + + var BID_RESPONSE_IMAGE_SYNC = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false, + dspPixels: ['pixelOne', 'pixelTwo', 'pixelThree'] + } + }; + + 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 } }; @@ -149,6 +188,18 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); + it('Verify parse response with no ad', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE_IS_NO_AD, request[0]); + expect(bids).to.have.lengthOf(0); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_IS_NO_AD, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -256,6 +307,32 @@ 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 () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(3); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); }); describe('gdpr tests', function () { @@ -425,6 +502,7 @@ describe('Smart bid adapter tests', function () { expect(bid.mediaType).to.equal('video'); expect(bid.vastUrl).to.equal('http://awesome.fake-vast.url'); expect(bid.vastXml).to.equal(''); + expect(bid.content).to.equal(''); expect(bid.width).to.equal(640); expect(bid.height).to.equal(480); expect(bid.creativeId).to.equal('zioeufg'); @@ -471,6 +549,357 @@ 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 () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + const OUTSTREAM_DEFAULT_PARAMS = [{ + adUnitCode: 'sas_43', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[800, 600]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '91', + target: 'test=prebid-outstream', + bidfloor: 0.430, + buId: '7579', + appName: 'Mozilla', + ckId: 43, + video: { + protocol: 7 + } + }, + requestId: 'efgh5679', + transactionId: 'zsfgzzga' + }]; + + var OUTSTREAM_BID_RESPONSE = { + body: { + cpm: 14, + width: 800, + height: 600, + creativeId: 'zioeufga', + currency: 'USD', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake-vast2.url', + ad: '', + cSyncUrl: 'http://awesome.fake2.csync.url' + } + }; + + it('Verify outstream video build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); + 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('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('91'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_43'); + expect(requestContent).to.not.have.property('pageDomain'); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7579'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(43); + expect(requestContent).to.have.property('isVideo').and.to.equal(false); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600); + }); + + it('Verify outstream parse response', function () { + const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); + const bids = spec.interpretResponse(OUTSTREAM_BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(14); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('http://awesome.fake-vast2.url'); + expect(bid.vastXml).to.equal(''); + expect(bid.content).to.equal(''); + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.creativeId).to.equal('zioeufga'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(OUTSTREAM_DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(OUTSTREAM_BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + }); + + 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 () { @@ -544,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 new file mode 100644 index 00000000000..104fa22a851 --- /dev/null +++ b/test/spec/modules/smarticoBidAdapter_spec.js @@ -0,0 +1,135 @@ +import {expect} from 'chai'; +import {spec} from 'modules/smarticoBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('smarticoBidAdapter', function () { + const adapter = newBidder(spec); + let bid = { + adUnitCode: 'adunit-code', + auctionId: '5kaj89l8-3456-2s56-c455-4g6h78jsdfgf', + bidRequestsCount: 1, + bidder: 'smartico', + bidderRequestId: '24081afs940568', + bidderRequestsCount: 1, + bidderWinsCount: 0, + bidId: '22499d052045', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + token: 'FNVzUGZn9ebpIOoheh3kEJ2GQ6H6IyMH39sHXaya', + placementId: 'testPlacementId' + }, + sizes: [ + [300, 250] + ], + transactionId: '34562345-4dg7-46g7-4sg6-45gdsdj8fd56' + } + let bidderRequests = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidderCode: 'myBidderCode', + bidderRequestId: '15246a574e859f', + bids: [bid], + refererInfo: { + canonicalUrl: '', + numIframes: 0, + reachedTop: true + } + } + describe('isBidRequestValid', function () { + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ bid ]; + let request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request via POST', function () { + expect(request.method).to.equal('POST'); + }); + it('must contain token', function() { + expect(request.data.bidParams[0].token).to.equal('FNVzUGZn9ebpIOoheh3kEJ2GQ6H6IyMH39sHXaya'); + }); + it('must contain auctionId', function() { + expect(request.data.auctionId).to.exist.and.to.be.a('string') + }); + it('must contain valid width and height', function() { + expect(request.data.bidParams[0]['banner-format-width']).to.exist.and.to.be.a('number') + expect(request.data.bidParams[0]['banner-format-height']).to.exist.and.to.be.a('number') + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + method: 'POST', + url: 'https://trmads.eu/preBidRequest', + bids: [bid], + data: [{ + token: 'FNVzUGZn9ebpIOoheh3kEJ2GQ6H6IyMH39sHXaya', + bidId: '22499d052045', + 'banner-format-width': 300, + 'banner-format-height': 250, + placementId: 'testPlacementId', + }] + }; + 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: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 250 }; let bidResponse2 = { id: '10865933907263800~9999~0', - impid: 'b9876abcd-300x600', + impid: 'b9876abcd', price: 1.99, crid: '9993-013', adm: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 600 }; + let bidRequest = { + data: { + id: '', + imp: [ + { + id: 'abc123', + banner: { + format: [ + { + w: 400, + h: 350 + } + ], + pos: 1 + } + } + ], + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverResponse; - beforeEach(function() { + beforeEach(function () { serverResponse = { body: { id: 'abc123', @@ -697,6 +930,26 @@ describe('synacormediaBidAdapter ', function () { }); it('should return 1 video bid when 1 bid is in the video response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 640, + h: 480 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverRespVideo = { body: { id: 'abcd1234', @@ -705,14 +958,16 @@ describe('synacormediaBidAdapter ', function () { bid: [ { id: '11339128001692337~9999~0', - impid: 'v2da7322b2df61f-640x480', + impid: 'v2da7322b2df61f', price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', - cat: [] + cat: [], + w: 640, + h: 480 } ], seat: '9999' @@ -722,11 +977,10 @@ describe('synacormediaBidAdapter ', function () { }; // serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverRespVideo); + let resp = spec.interpretResponse(serverRespVideo, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', - adId: '11339128001692337-9999-0', cpm: 0.45, width: 640, height: 480, @@ -736,6 +990,7 @@ describe('synacormediaBidAdapter ', function () { mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' }); @@ -743,11 +998,10 @@ describe('synacormediaBidAdapter ', function () { it('should return 1 bid when 1 bid is in the response', function () { serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -766,11 +1020,10 @@ describe('synacormediaBidAdapter ', function () { seat: '9999', bid: [bidResponse2], }); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(2); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -784,7 +1037,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp[1]).to.eql({ requestId: '9876abcd', - adId: '10865933907263800-9999-0', cpm: 1.99, width: 300, height: 600, @@ -798,7 +1050,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should not return a bid when no bid is in the response', function () { - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').that.is.empty; }); @@ -817,14 +1069,16 @@ describe('synacormediaBidAdapter ', function () { bid: [ { id: '11339128001692337~9999~0', - impid: 'v2da7322b2df61f-640x480', + impid: 'v2da7322b2df61f', price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', - cat: [] + cat: [], + w: 640, + h: 480 } ], seat: '9999' @@ -840,9 +1094,123 @@ describe('synacormediaBidAdapter ', function () { return config[key]; }); - let resp = spec.interpretResponse(serverRespVideo); - sandbox.restore(); - expect(resp[0].videoCacheKey).to.not.exist; + let resp = spec.interpretResponse(serverRespVideo, bidRequest); + sandbox.restore(); + expect(resp[0].videoCacheKey).to.not.exist; + }); + + it('should use video bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 300, + h: 250 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + let serverRespVideo = { + body: { + id: 'abcd1234', + seatbid: [ + { + bid: [ + { + id: '11339128001692337~9999~0', + impid: 'v2da7322b2df61f', + price: 0.45, + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', + adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', + adomain: ['psacentral.org'], + cid: 'bidder-crid', + crid: 'bidder-cid', + cat: [] + } + ], + seat: '9999' + } + ] + } + }; + let resp = spec.interpretResponse(serverRespVideo, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: '2da7322b2df61f', + cpm: 0.45, + width: 300, + height: 250, + creativeId: '9999_bidder-cid', + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', + ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, + videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', + vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' + }); + }); + + it('should use banner bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abc123', + imp: [ + { + banner: { + format: [{ + w: 400, + h: 350 + }] + }, + id: 'babc123' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + bidResponse = { + id: '10865933907263896~9998~0', + impid: 'babc123', + price: 0.13, + crid: '1022-250', + adm: '', + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + }; + + serverResponse.body.seatbid[0].bid.push(bidResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: 'abc123', + cpm: 0.13, + width: 400, + height: 350, + creativeId: '9998_1022-250', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/tapadIdSystem_spec.js b/test/spec/modules/tapadIdSystem_spec.js new file mode 100644 index 00000000000..bc31f1d37ba --- /dev/null +++ b/test/spec/modules/tapadIdSystem_spec.js @@ -0,0 +1,65 @@ +import { tapadIdSubmodule, graphUrl } from 'modules/tapadIdSystem.js'; +import * as utils from 'src/utils.js'; + +import { server } from 'test/mocks/xhr.js'; + +describe('TapadIdSystem', function () { + describe('getId', function() { + const config = { params: { companyId: 12345 } }; + it('should call to real time graph endpoint and handle valid response', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + expect(request.url).to.eq(`${graphUrl}?company_id=12345&tapad_id_type=TAPAD_ID`); + + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ tapadId: 'your-tapad-id' })); + expect(callbackSpy.lastCall.lastArg).to.eq('your-tapad-id'); + }); + + it('should remove stored tapadId if not found', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(404); + expect(callbackSpy.lastCall.lastArg).to.be.undefined; + }); + + it('should log message with invalid company id', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(403); + expect(logMessageSpy.lastCall.lastArg).to.eq('Invalid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id not given', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({}).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id is incorrect format', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({ params: { companyId: 'notANumber' } }).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + }); +}) diff --git a/test/spec/modules/taphypeBidAdapter_spec.js b/test/spec/modules/taphypeBidAdapter_spec.js deleted file mode 100644 index 6b36973814e..00000000000 --- a/test/spec/modules/taphypeBidAdapter_spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/taphypeBidAdapter.js'; - -describe('taphypeBidAdapterTests', function () { - it('validate_pub_params', function () { - expect(spec.isBidRequestValid({ - bidder: 'taphype', - params: { - placementId: 12345 - } - })).to.equal(true); - }); - - it('validate_generated_params', function () { - let bidRequestData = [{ - bidId: 'bid12345', - bidder: 'taphype', - params: { - placementId: 12345 - }, - sizes: [[300, 250]] - }]; - - let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; - - expect(req_data.bidId).to.equal('bid12345'); - }); - - it('validate_response_params', function () { - let bidRequestData = { - data: { - bidId: 'bid12345' - } - }; - - let serverResponse = { - body: { - price: 1.23, - ad: '', - size: '300,250' - } - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData); - expect(bids).to.have.lengthOf(1); - let bid = bids[0]; - expect(bid.cpm).to.equal(1.23); - 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('bid12345'); - expect(bid.ad).to.equal(''); - }); -}); diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js new file mode 100644 index 00000000000..123379eb29e --- /dev/null +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -0,0 +1,446 @@ +import { assert } from 'chai'; +import { spec } from 'modules/tappxBidAdapter.js'; +import { _checkParamDataType, _getHostInfo } from '../../../modules/tappxBidAdapter.js'; + +const c_BIDREQUEST = { + data: { + }, + bids: [ + { + bidder: 'tappx', + params: { + host: 'testing.ssp.tappx.com\/rtb\/v2\/', + tappxkey: 'pub-1234-android-1234', + endpoint: 'ZZ1234PBJS', + bidfloor: 0.05 + }, + crumbs: { + pubcid: 'df2144f7-673f-4440-83f5-cd4a73642d99' + }, + fpd: { + context: { + adServer: { + name: 'gam', + adSlot: '/19968336/header-bid-tag-0' + }, + pbAdSlot: '/19968336/header-bid-tag-0', + }, + }, + mediaTypes: { + banner: { + sizes: [ + [ + 320, + 480 + ] + ] + } + }, + adUnitCode: 'div-1', + transactionId: '47dd44e8-e7db-417c-a8f1-621a2e1a117d', + sizes: [ + [ + 320, + 480 + ] + ], + bidId: '2170932097e505', + bidderRequestId: '140ba7a1ab7aeb', + auctionId: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + } + ] +}; +const c_SERVERRESPONSE_B = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "\t", + w: 320, + h: 480, + lurl: 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_SERVERRESPONSE_V = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "Tappx<\/AdSystem>Tappx<\/AdTitle><\/Impression><\/Error>00:00:22<\/Duration><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/TrackingEvents><\/ClickThrough><\/ClickTracking><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + 'lurl': 'https:\/\/ssp.api.tappx.com\/rtb\/RTBv2Loss?id=5001829913749291152&ep=VZ12TESTCTV&au=test&bu=localhost&sz=6x6&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; + +const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +const c_BIDDERREQUEST_VOutstream = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"context": "outstream","playerSize":[640, 480],"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} + +describe('Tappx bid adapter', function () { + /** + * IS REQUEST VALID + */ + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); + }); + + it('should return false when params are missing', function () { + let badBidRequestParam = JSON.parse(JSON.stringify(c_BIDREQUEST)); + delete badBidRequestParam.bids[0].params; + assert.isFalse(spec.isBidRequestValid(badBidRequestParam.bids[0])); + }); + + it('should return false when tappxkey is missing', function () { + let badBidRequestTpxkey = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestTpxkey.bids[0].params.tappxkey; + assert.isFalse(spec.isBidRequestValid(badBidRequestTpxkey.bids[0])); + }); + + it('should return false when host is missing', function () { + let badBidRequestHost = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestHost.bids[0].params.host; + assert.isFalse(spec.isBidRequestValid(badBidRequestHost.bids[0])); + }); + + it('should return false when classic endpoint is missing', function () { + let badBidRequestClEp = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestClEp.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequestClEp.bids[0])); + }); + + it('should return true when endpoint is not set for new endpoints', function () { + let badBidRequestNwEp = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestNwEp.bids[0].params.endpoint; + badBidRequestNwEp.bids[0].params.host = 'zztesting.ssp.tappx.com/rtb/v2/'; + assert.isTrue(spec.isBidRequestValid(badBidRequestNwEp.bids[0])); + }); + + it('should return false mimes param is missing', function () { + let badBidRequest_mimes = c_BIDDERREQUEST_V; + delete badBidRequest_mimes.bids.mediaTypes.video; + badBidRequest_mimes.bids.mediaTypes.video = {}; + badBidRequest_mimes.bids.mediaTypes.video.context = 'instream'; + badBidRequest_mimes.bids.mediaTypes.video.playerSize = [320, 250]; + assert.isFalse(spec.isBidRequestValid(badBidRequest_mimes.bids), badBidRequest_mimes); + }); + + it('should return false for not instream/outstream requests', function () { + let badBidRequest_v = c_BIDDERREQUEST_V; + delete badBidRequest_v.bids.mediaTypes.banner; + badBidRequest_v.bids.mediaTypes.video = {}; + badBidRequest_v.bids.mediaTypes.video.context = ''; + badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; + assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); + }); + }); + + /** + * BUILD REQUEST TEST + */ + describe('buildRequest', function () { + // Web Test + let validBidRequests = c_VALIDBIDREQUESTS; + let validBidRequests_V = c_VALIDBIDREQUESTS; + let validBidRequests_Voutstream = c_VALIDBIDREQUESTS; + // App Test + let validAppBidRequests = c_VALIDBIDAPPREQUESTS; + + let bidderRequest = c_BIDDERREQUEST_B; + let bidderRequest_V = c_BIDDERREQUEST_V; + let bidderRequest_VOutstream = c_BIDDERREQUEST_VOutstream; + + it('should add gdpr/usp consent information to the request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.regs.gdpr).to.exist.and.to.be.true; + expect(payload.user.ext.consent).to.exist.and.to.equal(c_CONSENTSTRING); + expect(payload.regs.ext.us_privacy).to.exist; + }); + + it('should properly build a banner request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should properly build a video request', function () { + delete validBidRequests_V[0].mediaTypes.banner + validBidRequests_V[0].mediaTypes.video = {}; + validBidRequests_V[0].mediaTypes.video.playerSize = [640, 480]; + validBidRequests_V[0].mediaTypes.video.context = 'instream'; + validBidRequests_V[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + + bidderRequest_V.bids.mediaTypes.context = 'instream'; + + const request = spec.buildRequests(validBidRequests_V, bidderRequest_V); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].video).to.not.equal(null); + }); + + it('should properly build a video outstream request', function () { + delete validBidRequests_Voutstream[0].mediaTypes.banner + validBidRequests_Voutstream[0].mediaTypes.video = {}; + validBidRequests_Voutstream[0].mediaTypes.video.playerSize = [640, 480]; + validBidRequests_Voutstream[0].mediaTypes.video.context = 'outstream'; + validBidRequests_Voutstream[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + + bidderRequest_VOutstream.bids.mediaTypes.context = 'outstream'; + + const request = spec.buildRequests(validBidRequests_Voutstream, bidderRequest_VOutstream); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].video).to.not.equal(null); + }); + + it('should set user eids array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + const data = JSON.parse(request[0].data); + expect(data.user.ext.eids, data).to.not.be.null; + expect(data.user.ext.eids[0]).to.have.keys(['source', 'uids']); + }); + + it('should properly build a banner request with app params', function () { + const request = spec.buildRequests(validAppBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should properly build a ext optional object', function() { + let extBidRequest = c_VALIDBIDREQUESTS; + extBidRequest[0].params.ext = {'optionalData': '1234'}; + let extBidderRequest = c_BIDDERREQUEST_B; + extBidderRequest.bids[0].ext = {'optionalData': '1234'}; + + const request = spec.buildRequests(extBidRequest, extBidderRequest); + const data = JSON.parse(request[0].data); + expect(data.imp[0].ext.bidder.ext).to.be.an('object'); + expect(data.imp[0].ext.bidder.ext.optionalData).to.be.equal('1234'); + }); + + it('should ignore ext optional if is not a object', function() { + let badExtBidRequest = c_VALIDBIDREQUESTS; + badExtBidRequest[0].params.ext = 'stringValue'; + let badExtBidderRequest = c_BIDDERREQUEST_B; + badExtBidderRequest.bids[0].ext = 'stringValue'; + + const request = spec.buildRequests(badExtBidRequest, badExtBidderRequest); + const data = JSON.parse(request[0].data); + expect(data.imp[0].ext.bidder.ext).not.to.be.an('string'); + expect(data.imp[0].ext.bidder.ext).to.be.an('undefined'); + expect(data.imp[0].ext.bidder).to.not.have.property('ext') + }); + }); + + /** + * INTERPRET RESPONSE TESTS + */ + describe('interpretResponse', function () { + it('receive banner reponse with single placement', function () { + const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); + const bid = bids[0]; + expect(bid.cpm).to.exist; + expect(bid.ad).to.match(/^', + '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('TRY'); + expect(result[0].netRevenue).to.equal(false); + }); + }) +}) 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 596e8a3e2d9..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'); @@ -146,47 +147,47 @@ describe('Trion adapter tests', function () { expect(bidUrlParams).to.include(getPublisherUrl()); }); - describe('webdriver', function () { - let originalWD; - - beforeEach(function () { - originalWD = window.navigator.webdriver; - }); - - afterEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return originalWD; - }); - }); - - describe('is present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 1; - }); - }); - - it('when there is non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=1'); - }); - }); - - describe('is not present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 0; - }); - }); - - it('when there is not non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=0'); - }); - }); - }); + // describe('webdriver', function () { + // let originalWD; + + // beforeEach(function () { + // originalWD = window.navigator.webdriver; + // }); + + // afterEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return originalWD; + // }); + // }); + + // describe('is present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 1; + // }); + // }); + + // it('when there is non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=1'); + // }); + // }); + + // describe('is not present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 0; + // }); + // }); + + // it('when there is not non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=0'); + // }); + // }); + // }); describe('document', function () { let originalHD; @@ -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 73373293114..30377ec0a5d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -4,26 +4,31 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { deepClone } from 'src/utils.js'; import { config } from 'src/config.js'; import prebid from '../../../package.json'; +import * as utils from 'src/utils.js'; const ENDPOINT = 'https://tlx.3lift.com/header/auction?'; const GDPR_CONSENT_STR = 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY'; describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); + let bid, instreamBid; + let sandbox; - 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 = { + this.beforeEach(() => { + bid = { bidder: 'triplelift', params: { inventoryCode: '12345', floor: 1.0, }, + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', @@ -31,28 +36,70 @@ describe('triplelift adapter', function () { 'auctionId': '1d1a030790a475', }; + instreamBid = { + bidder: 'triplelift', + params: { + inventoryCode: 'insteam_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + }) + + 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 for valid bid request', function () { expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - inventoryCode: 'another_inv_code', - floor: 0.05 - }; + bid.params.inventoryCode = 'another_inv_code'; expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when required params found - instream', function () { + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(true); + }); + + it('should return true when required params found - instream - 2', function () { + delete instreamBid.mediaTypes.playerSize; + delete instreamBid.params.video.w; + delete instreamBid.params.video.h; + // the only required param is inventoryCode + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(true); + }); + it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - floor: 1.0 - }; + delete bid.params.inventoryCode; expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required params are not passed - instream', function () { + delete instreamBid.params.inventoryCode; + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -81,6 +128,14 @@ describe('triplelift adapter', function () { inventoryCode: '12345', floor: 1.0, }, + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, adUnitCode: 'adunit-code', sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], bidId: '30b31c1838de1e', @@ -88,6 +143,209 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + ext: { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + } + } + } + }, + { + bidder: 'triplelift', + params: { + inventoryCode: 'insteam_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and outstream video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and incomplete video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // incomplete banner and incomplete video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + + }, + banner: { + + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and instream video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and outream video and native + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + }, + native: { + + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, } ]; @@ -103,6 +361,13 @@ describe('triplelift adapter', function () { height: 250, ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + }, + { + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 0.01, + ad: 'The Trade Desk', + tlx_source: 'hdx' } ], refererInfo: { @@ -113,6 +378,10 @@ describe('triplelift adapter', function () { gdprApplies: true }, }; + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); }); it('exists and is an object', function () { @@ -120,6 +389,11 @@ describe('triplelift adapter', function () { expect(request).to.exist.and.to.be.a('object'); }); + it('should be able find video object from the instream request', function () { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].video).to.exist.and.to.be.a('object'); + }); + it('should only parse sizes that are of the proper length and format', function () { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].banner.format).to.have.length(2); @@ -133,6 +407,29 @@ describe('triplelift adapter', function () { expect(payload.imp[0].tagid).to.equal('12345'); expect(payload.imp[0].floor).to.equal(1.0); expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + expect(payload.imp[1].tagid).to.equal('insteam_test'); + expect(payload.imp[1].floor).to.equal(1.0); + expect(payload.imp[1].video).to.exist.and.to.be.a('object'); + // banner and outstream video + expect(payload.imp[2]).to.not.have.property('video'); + expect(payload.imp[2]).to.have.property('banner'); + expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // banner and incomplete video + expect(payload.imp[3]).to.not.have.property('video'); + expect(payload.imp[3]).to.have.property('banner'); + expect(payload.imp[3].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // incomplete mediatypes.banner and incomplete video + expect(payload.imp[4]).to.not.have.property('video'); + expect(payload.imp[4]).to.have.property('banner'); + expect(payload.imp[4].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // banner and instream video + expect(payload.imp[5]).to.not.have.property('banner'); + expect(payload.imp[5]).to.have.property('video'); + expect(payload.imp[5].video).to.exist.and.to.be.a('object'); + // banner and outream video and native + expect(payload.imp[6]).to.not.have.property('video'); + expect(payload.imp[6]).to.have.property('banner'); + expect(payload.imp[6].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); it('should add tdid to the payload if included', function () { @@ -209,15 +506,16 @@ describe('triplelift adapter', function () { }); }); - it('should add user ids from multiple bid requests', function () { + it('should consolidate user ids from multiple bid requests', function () { const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; + const pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; const bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId } }, - { ...bidRequests[0], userId: { idl_env: idlEnvId } }, - { ...bidRequests[0], userId: { criteoId: criteoId } } + { ...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 } } ]; const request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); @@ -252,10 +550,22 @@ describe('triplelift adapter', function () { ext: { rtiPartner: 'criteoId' } } ] + }, + { + source: 'pubcid.org', + uids: [ + { + id: '3261d8ad-435d-481d-abd1-9f1a9ec99f0e', + ext: { rtiPartner: 'pubcid' } + } + ] } ] } }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(4); }); it('should return a query string for TL call', function () { @@ -300,79 +610,97 @@ describe('triplelift adapter', function () { expect(payload.ext).to.deep.equal(undefined); }); it('should get floor from floors module if available', function() { - const floorInfo = { - currency: 'USD', - floor: 1.99 - }; + let floorInfo; bidRequests[0].getFloor = () => floorInfo; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + + // standard float response; expected functionality of floors module + floorInfo = { currency: 'USD', floor: 1.99 }; + let request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].floor).to.equal(1.99); + + // if string response, convert to float + floorInfo = { currency: 'USD', floor: '1.99' }; + request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].floor).to.equal(1.99); }); - }); + it('should call getFloor with the correct parameters based on mediaType', function() { + bidRequests.forEach(request => { + request.getFloor = () => {}; + sinon.spy(request, 'getFloor') + }); - describe('interpretResponse', function () { - let response = { - body: { - bids: [ - { - imp_id: 0, - cpm: 1.062, - width: 300, - height: 250, - ad: 'ad-markup', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + + // banner + expect(bidRequests[0].getFloor.calledWith({ + currency: 'USD', + mediaType: 'banner', + size: '*' + })).to.be.true; + + // instream + expect(bidRequests[1].getFloor.calledWith({ + currency: 'USD', + mediaType: 'video', + size: '*' + })).to.be.true; + + // banner and incomplete video (POST will only include banner) + expect(bidRequests[3].getFloor.calledWith({ + currency: 'USD', + mediaType: 'banner', + size: '*' + })).to.be.true; + + // banner and instream (POST will only include video) + expect(bidRequests[5].getFloor.calledWith({ + currency: 'USD', + mediaType: 'video', + size: '*' + })).to.be.true; + }); + it('should send global config fpd if kvps are available', function() { + const sens = null; + const category = ['news', 'weather', 'hurricane']; + const pmp_elig = 'true'; + const ortb2 = { + site: { + pmp_elig: pmp_elig, + ext: { + data: { + category: category + } } - ] - } - }; - let bidderRequest = { - bidderCode: 'triplelift', - auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', - bidderRequestId: '5c55612f99bc11', - bids: [ - { - imp_id: 0, - cpm: 1.062, - width: 300, - height: 250, - ad: 'ad-markup', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + }, + user: { + sens: sens, } - ], - refererInfo: { - referer: 'https://examplereferer.com' - }, - gdprConsent: { - consentString: GDPR_CONSENT_STR, - gdprApplies: true } - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '3db3773286ee59', - cpm: 1.062, - width: 300, - height: 250, - netRevenue: true, - ad: 'ad-markup', - creativeId: 29681110, - dealId: '', - currency: 'USD', - ttl: 33, - tl_source: 'tlx', - } - ]; - let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); - expect(result).to.have.length(1); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + ortb2 + }; + return utils.deepAccess(config, key); + }); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const { data: payload } = request; + expect(payload.ext.fpd.user).to.not.exist; + expect(payload.ext.fpd.context.data).to.haveOwnProperty('category'); + expect(payload.ext.fpd.context).to.haveOwnProperty('pmp_elig'); + }); + it('should send ad unit fpd if kvps are available', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[1].fpd).to.not.exist; }); + }); - it('should return multiple responses to support SRA', function () { - let response = { + describe('interpretResponse', function () { + let response, bidderRequest; + this.beforeEach(() => { + response = { body: { bids: [ { @@ -383,20 +711,20 @@ describe('triplelift adapter', function () { ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', tl_source: 'tlx', + advertiser_name: 'fake advertiser name', + adomain: ['basspro.com', 'internetalerts.org'] }, { - imp_id: 0, - cpm: 1.9, - width: 300, - height: 600, - ad: 'ad-markup-2', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 9.99, + ad: 'The Trade Desk', + tlx_source: 'hdx' } ] } }; - let bidderRequest = { + bidderRequest = { bidderCode: 'triplelift', auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', bidderRequestId: '5c55612f99bc11', @@ -405,19 +733,33 @@ describe('triplelift adapter', function () { imp_id: 0, cpm: 1.062, width: 300, - height: 600, + height: 250, ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', tl_source: 'tlx', + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + bidId: '30b31c1838de1e', }, { - imp_id: 0, - cpm: 1.9, - width: 300, - height: 250, - ad: 'ad-markup-2', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 9.99, + ad: 'The Trade Desk', + tlx_source: 'hdx', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', } ], refererInfo: { @@ -428,8 +770,63 @@ describe('triplelift adapter', function () { gdprApplies: true } }; + }) + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '30b31c1838de1e', + cpm: 1.062, + width: 300, + height: 250, + netRevenue: true, + ad: 'ad-markup', + creativeId: 29681110, + dealId: '', + currency: 'USD', + ttl: 33, + tl_source: 'tlx', + meta: {} + }, + { + requestId: '30b31c1838de1e', + cpm: 1.062, + width: 300, + height: 250, + netRevenue: true, + ad: 'The Trade Desk', + creativeId: 29681110, + dealId: '', + currency: 'USD', + ttl: 33, + tl_source: 'hdx', + mediaType: 'video', + vastXml: 'The Trade Desk', + meta: {} + } + ]; let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); 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])); + }); + + it('should return multiple responses to support SRA', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result).to.have.length(2); + }); + + it('should include the advertiser name in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[0].meta.advertiserName).to.equal('fake advertiser name'); + expect(result[1].meta).to.not.have.key('advertiserName'); + }); + + it('should include the advertiser domain array in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[0].meta.advertiserDomains[0]).to.equal('basspro.com'); + expect(result[0].meta.advertiserDomains[1]).to.equal('internetalerts.org'); + expect(result[1].meta).to.not.have.key('advertiserDomains'); }); }); diff --git a/test/spec/modules/truereachBidAdapter_spec.js b/test/spec/modules/truereachBidAdapter_spec.js index 5fe053ceb91..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 () { @@ -81,4 +81,25 @@ describe('truereachBidAdapterTests', function () { expect(bid.netRevenue).to.equal(false); expect(bid.meta.advertiserDomains[0]).to.equal('https://www.momagic.com/'); }); + + describe('user_sync', function() { + const user_sync_url = 'http://ads.momagic.com/jsp/usersync.jsp'; + it('register_iframe_pixel_if_iframeEnabled_is_true', function() { + let syncs = spec.getUserSyncs( + {iframeEnabled: true} + ); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal(user_sync_url); + }); + + it('if_pixelEnabled_is_true', function() { + let syncs = spec.getUserSyncs( + {pixelEnabled: true} + ); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(0); + }); + }); }); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 9e0aad9b36f..faaae620ad2 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/trustxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('TrustXAdapter', function () { const adapter = newBidder(spec); @@ -39,55 +40,82 @@ 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; + function parseRequest(data) { + return JSON.parse(data); } - const bidderRequest = { - refererInfo: { - referer: 'https://example.com' - } + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: '22edbae2733bf6', + auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + timeout: 3000 }; - const referrer = bidderRequest.refererInfo.referer; + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); let bidRequests = [ { 'bidder': 'trustx', 'params': { - 'uid': '43' + 'uid': '43', + 'bidFloor': 1.25, + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '44', }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', }, { 'bidder': 'trustx', 'params': { - 'uid': '43' + 'uid': '45', }, 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90], [300, 250]], + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }, 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', }, { 'bidder': 'trustx', 'params': { - 'uid': '45' + 'uid': '41', }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '42dbe3a7168a6a', + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', } ]; @@ -95,143 +123,491 @@ describe('TrustXAdapter', 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', '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$'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); }); - it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); + it('make possible to process request without mediaTypes', function () { + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], 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', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); }); - it('pt parameter must be "gross" if params.priceType === "gross"', function () { - bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests, bidderRequest); + it('should attach valid params to the video tag', function () { + const request = spec.buildRequests(bidRequests.slice(0, 3), 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', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }] + }); }); - it('pt parameter must be "net" or "gross"', function () { - bidRequests[1].params.priceType = 'some'; + it('should support mixed mediaTypes', 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', '43,43,45'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - delete bidRequests[1].params.priceType; + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600, + 'protocols': [1, 2, 3] + } + }] + }); }); 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); + const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const request = spec.buildRequests(bidRequests, gdprBidderRequest); 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'); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('consent', 'AAA'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('gdpr', 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); + 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('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '0'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); }); - 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); + it('if userId is present payload must have user.ext param with right keys', function () { + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userIdAsEids: eids + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); 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'); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext.eids).to.deep.equal(eids); }); - 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); + it('if schain is present payload must have source.ext.schain param', function () { + const schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const bidRequestsWithSChain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithSChain, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('us_privacy', '1YNN'); + expect(payload).to.have.property('source'); + expect(payload.source).to.have.property('ext'); + expect(payload.source.ext).to.have.property('schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); }); - 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 + it('if content and segment is present in jwTargeting, payload must have right params', function () { + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { + return Object.assign({ + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } } } - } - ); + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithJwTargeting, 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: 'iow_labs_pub_data', + segment: [ + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} + ] + }]); + expect(payload).to.have.property('site'); + expect(payload.site.content).to.deep.equal(jsContent); + }); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + it('if segment is present in permutive targeting, payload must have right params', function () { + const permSegments = ['test_perm_1', '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.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' + expect(payload).to.have.property('user'); + expect(payload.user.data).to.deep.equal([{ + name: 'permutive', + segment: [ + {name: 'p_standard', value: permSegments[0]}, + {name: 'p_standard', value: permSegments[1]} + ] }]); }); + + 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,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({ + '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); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(2000); + getConfigStub.restore(); + }); + 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); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(3000); + getConfigStub.restore(); + }); + 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 = { + 'currency': 'USD', + 'floor': 1.50 + }; + const bidRequest = Object.assign({ + getFloor: (_) => { + return floorTestData; + } + }, bidRequests[1]); + it('should return the value from getFloor if present', function () { + 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); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + 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); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + 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); + }); + }); }); 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'}, @@ -262,11 +638,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': ['somedomain.com'] + }, } ]; @@ -283,7 +661,7 @@ describe('TrustXAdapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', + 'bidId': '659423fff799cb', 'bidderRequestId': '2c2bb1972df9a', 'auctionId': '1fa09aee5c8c99', }, @@ -313,18 +691,20 @@ describe('TrustXAdapter', function () { const request = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '300bfeb0d71a5b', + 'requestId': '659423fff799cb', 'cpm': 1.15, 'creativeId': 43, 'dealId': undefined, 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': ['somedomain.com'] + }, }, { 'requestId': '4dff80cc4ee346', @@ -334,11 +714,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '5703af74d0472a', @@ -348,11 +730,13 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, } ]; @@ -403,11 +787,11 @@ describe('TrustXAdapter', function () { 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 = [ { @@ -476,11 +860,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '4e111f1b66e4', @@ -490,11 +876,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '26d6f897b516', @@ -504,11 +892,13 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '326bde7fbf69', @@ -518,11 +908,29 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'trustx', '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': [] + }, } ]; @@ -532,8 +940,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 = [ { @@ -580,11 +988,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, }, { 'requestId': '57b2ebe70e16', @@ -594,11 +1004,13 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, } ]; @@ -643,8 +1055,8 @@ 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'}], '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'} ]; const request = spec.buildRequests(bidRequests); const expectedResponse = [ @@ -655,11 +1067,13 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' @@ -722,9 +1136,9 @@ 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); const expectedResponse = [ @@ -735,11 +1149,13 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' @@ -753,11 +1169,13 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', - 'netRevenue': true, + 'netRevenue': false, 'ttl': 360, + 'meta': { + 'advertiserDomains': [] + }, 'vastXml': '\n<\/Ad>\n<\/VAST>', 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' @@ -771,11 +1189,13 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', '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/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 b5f29287f81..ac788e537e2 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/ucfunnelBidAdapter.js'; import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; - const URL = 'https://hb.aralego.com/header'; const BIDDER_CODE = 'ucfunnel'; @@ -9,15 +8,28 @@ const bidderRequest = { uspConsent: '1YNN' }; +const userId = { + 'criteoId': 'vYlICF9oREZlTHBGRVdrJTJCUUJnc3U2ckNVaXhrV1JWVUZVSUxzZmJlcnJZR0ZxbVhFRnU5bDAlMkJaUWwxWTlNcmdEeHFrJTJGajBWVlV4T3lFQ0FyRVcxNyUyQlIxa0lLSlFhcWJpTm9PSkdPVkx0JTJCbzlQRTQlM0Q', + 'id5id': {'uid': 'ID5-8ekgswyBTQqnkEKy0ErmeQ1GN5wV4pSmA-RE4eRedA'}, + 'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + 'parrableId': {'eid': '01.1608624401.fe44bca9b96873084a0d4e9d0ac5729f13790ba8f8e58fa4707b6b3c096df91c6b5f254992bdad4ab1dd4a89919081e9b877d7a039ac3183709277665bac124f28e277d109f0ff965058'}, + 'pubcid': 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', + 'tdid': 'D6885E90-2A7A-4E0F-87CB-7734ED1B99A3', + 'haloId': {}, + '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', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + userId: userId, 'schain': { 'ver': '1.0', 'complete': 1, @@ -50,7 +62,8 @@ const validBannerBidRes = { adm: '
', cpm: 1.01, height: 250, - width: 300 + width: 300, + crid: 'test-crid' }; const invalidBannerBidRes = ''; @@ -112,6 +125,13 @@ const validNativeBidRes = { width: 1 }; +const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 +}; + describe('ucfunnel Adapter', function () { describe('request', function () { it('should validate bid request', function () { @@ -139,6 +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!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'); }); @@ -151,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 () { @@ -253,18 +310,18 @@ describe('ucfunnel Adapter', function () { describe('cookie sync', function () { describe('cookie sync iframe', function () { - const result = spec.getUserSyncs({'iframeEnabled': true}); + const result = spec.getUserSyncs({'iframeEnabled': true}, null, gdprConsent); it('should return cookie sync iframe info', function () { expect(result[0].type).to.equal('iframe'); - expect(result[0].url).to.equal('https://cdn.aralego.net/ucfad/cookie/sync.html'); + expect(result[0].url).to.equal('https://cdn.aralego.net/ucfad/cookie/sync.html?gdpr=1&euconsent-v2=CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA&'); }); }); describe('cookie sync image', function () { - const result = spec.getUserSyncs({'pixelEnabled': true}); + const result = spec.getUserSyncs({'pixelEnabled': true}, null, gdprConsent); it('should return cookie sync image info', function () { expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal('https://sync.aralego.com/idSync'); + expect(result[0].url).to.equal('https://sync.aralego.com/idSync?gdpr=1&euconsent-v2=CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA&'); }); }); }); 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 e4218019e0d..0faa321be5f 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -24,13 +24,49 @@ const invalidBidReq = { auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' }; -const bidReq = [{ +const videoBidReq = [{ adUnitCode: 'div-gpt-ad-1460505748561-0', bidder: BIDDER_CODE, params: { placementId: '10433394', + publisherId: 12345, + video: { + id: 123, + skippable: true, + playbackMethod: 2, + maxDuration: 30 + } + }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}, +{ + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidder: BIDDER_CODE, + params: { + placementId: '10433395', publisherId: 12345 }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; +const bidReq = [{ + 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' @@ -58,7 +94,8 @@ const bidReqUserIds = [{ userId: { idl_env: '1111', tdid: '123456', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + id5id: { uid: '1111' } } }, { @@ -147,6 +184,20 @@ const bidResArray = [ ttl: 360 } ]; +const bidVideoResponse = [ + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + mediaType: 'video', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; let element; let sandbox; @@ -241,6 +292,23 @@ describe('Undertone Adapter', () => { expect(bid2.publisherId).to.equal(12345); expect(bid2.params).to.be.an('object'); }); + it('should send video fields correctly', function () { + const request = spec.buildRequests(videoBidReq, bidderReq); + const bidVideo = JSON.parse(request.data)['x-ut-hb-params'][0]; + const bidVideo2 = JSON.parse(request.data)['x-ut-hb-params'][1]; + + expect(bidVideo.mediaType).to.equal('video'); + expect(bidVideo.video).to.be.an('object'); + expect(bidVideo.video.playerSize).to.be.an('array'); + expect(bidVideo.video.streamType).to.equal('outstream'); + expect(bidVideo.video.playbackMethod).to.equal(2); + expect(bidVideo.video.maxDuration).to.equal(30); + expect(bidVideo.video.skippable).to.equal(true); + + expect(bidVideo2.video.skippable).to.equal(null); + expect(bidVideo2.video.maxDuration).to.equal(null); + expect(bidVideo2.video.playbackMethod).to.equal(null); + }); it('should send all userIds data to server', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); const bidCommons = JSON.parse(request.data)['commons']; @@ -249,6 +317,7 @@ describe('Undertone Adapter', () => { expect(bidCommons.uids.tdid).to.equal('123456'); expect(bidCommons.uids.idl_env).to.equal('1111'); expect(bidCommons.uids.digitrustid.data.id).to.equal('DTID'); + expect(bidCommons.uids.id5id.uid).to.equal('1111'); }); it('should send page sizes sizes correctly', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); @@ -289,6 +358,7 @@ describe('Undertone Adapter', () => { expect(bid.cpm).to.equal(100); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); + expect(bid.meta.advertiserDomains).to.deep.equal([]); expect(bid.creativeId).to.equal(15); expect(bid.currency).to.equal('USD'); expect(bid.netRevenue).to.equal(true); @@ -303,6 +373,13 @@ describe('Undertone Adapter', () => { it('should only use valid bid responses', () => { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); + + it('should detect video response', () => { + const videoResult = spec.interpretResponse({body: bidVideoResponse}); + const vbid = videoResult[0]; + + expect(vbid.mediaType).to.equal('video'); + }); }); describe('getUserSyncs', () => { diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index 4c56c37700b..615eac2ecf2 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]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'ea0aa332-a6e1-4474-8180-83720e6b87bc', - sizes: [[300, 250]], + 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]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + 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]] + sizes: [ + [ + 300, 250 + ], + [ + 336, 280 + ] + ] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + 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', @@ -234,28 +285,47 @@ const openRTBRequest = { { id: '216255f234b602', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + }, { + w: 336, + h: 280 + } + ] }, secure: 1, bidfloor: 0, tagid: 'rectangle-ad-1' - }, - { + }, { id: '31e2b28ced2475', banner: { - w: '300', - h: '250' + 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' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + } + ] }, secure: 1, bidfloor: 0, @@ -277,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' @@ -287,7 +356,8 @@ const openRTBRequest = { source: { ext: { stype: 'prebid_uncn', - bidder: 'unicorn' + bidder: 'unicorn', + prebid_version: '1.0' } } }; @@ -319,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'], @@ -342,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'], @@ -377,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"},"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"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"}}}' + 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"}}}' }; const interpretedBids = [ @@ -387,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, @@ -403,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 9ece8927ef7..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,6 +697,10 @@ describe('UnrulyAdapter', function () { netRevenue: true, creativeId: 'mockBidId', ttl: 360, + 'meta': { + 'mediaType': 'video', + 'videoContext': 'outstream' + }, currency: 'USD', renderer: fakeRenderer, mediaType: 'video' @@ -159,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: { @@ -175,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); @@ -183,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' }; @@ -198,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: {} @@ -228,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); @@ -255,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); @@ -266,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 d038074cb10..0190bceca70 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1,11 +1,15 @@ import { attachIdSystem, auctionDelay, + coreStorage, init, requestBidsHook, + setStoredConsentData, + setStoredValue, setSubmoduleRegistry, syncDelay, - coreStorage + PBJS_USER_ID_OPTOUT_NAME, + findRootDomain, } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -13,36 +17,50 @@ import * as utils from 'src/utils.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {getGlobal} from 'src/prebidGlobal.js'; +import { + requestBidsHook as consentManagementRequestBidsHook, + resetConsentData, + setConsentConfig +} from 'modules/consentManagement.js'; +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'; +import {dmdIdSubmodule} from 'modules/dmdIdSystem.js'; import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; +import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; +import {nextrollIdSubmodule} from 'modules/nextrollIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; -import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; -import {server} from 'test/mocks/xhr.js'; +import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; +import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; +import {haloIdSubmodule} from 'modules/haloIdSystem.js'; +import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; +import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; +import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; +import {getPrebidInternal} from 'src/utils.js'; +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' let assert = require('chai').assert; let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const CONSENT_LOCAL_STORAGE_NAME = '_pbjs_userid_consent_data'; -describe('User ID', function() { - function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7, configArr8) { +describe('User ID', function () { + function getConfigMock(...configArrays) { return { userSync: { syncDelay: 0, - userIds: [ - (configArr1 && configArr1.length >= 3) ? getStorageMock.apply(null, configArr1) : null, - (configArr2 && configArr2.length >= 3) ? getStorageMock.apply(null, configArr2) : null, - (configArr3 && configArr3.length >= 3) ? getStorageMock.apply(null, configArr3) : null, - (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null, - (configArr5 && configArr5.length >= 3) ? getStorageMock.apply(null, configArr5) : null, - (configArr6 && configArr6.length >= 3) ? getStorageMock.apply(null, configArr6) : null, - (configArr7 && configArr7.length >= 3) ? getStorageMock.apply(null, configArr7) : null, - (configArr8 && configArr8.length >= 3) ? getStorageMock.apply(null, configArr8) : null - ].filter(i => i) + userIds: configArrays.map(configArray => (configArray && configArray.length >= 3) ? getStorageMock.apply(null, configArray) : null) } } } @@ -62,7 +80,7 @@ describe('User ID', function() { code, mediaTypes: {banner: {}, native: {}}, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}, {bidder: 'anotherSampleBidder', params: {placementId: 'banner-only-bidder'}}] }; } @@ -80,31 +98,39 @@ describe('User ID', function() { return cfg; } - before(function() { - coreStorage.setCookie('_pubcid_optout', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('_pbjs_id_optout'); - localStorage.removeItem('_pubcid_optout'); + function findEid(eids, source) { + return find(eids, (eid) => { + if (eid.source === source) { return true; } + }); + } + + before(function () { + localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); + }); + + beforeEach(function () { + coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); }); - describe('Decorate Ad Units', function() { - beforeEach(function() { + describe('Decorate Ad Units', function () { + beforeEach(function () { coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); }); - after(function() { + after(function () { coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', '', EXPIRED_COOKIE_DATE); }); - it('Check same cookie behavior', function() { + it('Check same cookie behavior', function () { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -113,7 +139,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'])); @@ -139,7 +165,7 @@ describe('User ID', function() { assert.deepEqual(innerAdUnits1, innerAdUnits2); }); - it('Check different cookies', function() { + it('Check different cookies', function () { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -147,7 +173,7 @@ describe('User ID', function() { let pubcid1; let pubcid2; - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); requestBidsHook((config) => { @@ -167,7 +193,7 @@ describe('User ID', function() { }); }); - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); requestBidsHook((config) => { @@ -190,11 +216,11 @@ describe('User ID', function() { expect(pubcid1).to.not.equal(pubcid2); }); - it('Use existing cookie', function() { + it('Use existing cookie', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; - setSubmoduleRegistry([pubCommonIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie'])); requestBidsHook((config) => { @@ -210,17 +236,19 @@ describe('User ID', function() { }); }); }); - // Because the cookie exists already, there should be no setCookie call by default - expect(coreStorage.setCookie.callCount).to.equal(0); + // 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() { + it('Extend cookie', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; 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) => { @@ -236,17 +264,18 @@ describe('User ID', function() { }); }); }); - // Because extend is true, the cookie will be updated even if it exists already - expect(coreStorage.setCookie.callCount).to.equal(1); + // Because extend is true, the cookie will be updated even if it exists already. The second setCookie call + // is for storing consentData + expect(coreStorage.setCookie.callCount).to.equal(2); }); - it('Disable auto create', function() { + it('Disable auto create', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; 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) => { @@ -258,11 +287,12 @@ describe('User ID', function() { expect(bid).to.not.have.deep.nested.property('userIdAsEids'); }); }); - expect(coreStorage.setCookie.callCount).to.equal(0); + // setCookie is called once in order to store consentData + expect(coreStorage.setCookie.callCount).to.equal(1); }); - it('pbjs.getUserIds', function() { - setSubmoduleRegistry([pubCommonIdSubmodule]); + it('pbjs.getUserIds', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig({ userSync: { @@ -276,8 +306,8 @@ describe('User ID', function() { expect((getGlobal()).getUserIds()).to.deep.equal({pubcid: '11111'}); }); - it('pbjs.getUserIdsAsEids', function() { - setSubmoduleRegistry([pubCommonIdSubmodule]); + it('pbjs.getUserIdsAsEids', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig({ userSync: { @@ -290,57 +320,195 @@ describe('User ID', function() { expect(typeof (getGlobal()).getUserIdsAsEids).to.equal('function'); expect((getGlobal()).getUserIdsAsEids()).to.deep.equal(createEidsArray((getGlobal()).getUserIds())); }); + + it('pbjs.refreshUserIds refreshes', 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(typeof (getGlobal()).refreshUserIds).to.equal('function'); + + getGlobal().getUserIds(); // force initialization + + // update config so that getId will be called + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', + storage: {name: 'mockid', type: 'cookie'}, + }] + } + }); + + getGlobal().refreshUserIds(); + 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); + + let sandbox = sinon.createSandbox(); + let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}}); + let refreshUserIdsCallback = sandbox.stub(); + + let mockIdSystem = { + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: mockIdCallback + }; + + let refreshedIdCallback = sandbox.stub().returns({id: {'REFRESH': '1111'}}); + + let refreshedIdSystem = { + name: 'refreshedId', + decode: function(value) { + return { + 'refresh': value['REFRESH'] + }; + }, + getId: refreshedIdCallback + }; + + setSubmoduleRegistry([refreshedIdSystem, mockIdSystem]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'mockId', + storage: {name: 'MOCKID', type: 'cookie'}, + }, + { + name: 'refreshedId', + storage: {name: 'refreshedid', type: 'cookie'}, + } + ] + } + }); + + getGlobal().getUserIds(); // force initialization + + getGlobal().refreshUserIds({submoduleNames: 'refreshedId'}, refreshUserIdsCallback); + + expect(refreshedIdCallback.callCount).to.equal(2); + expect(mockIdCallback.callCount).to.equal(1); + expect(refreshUserIdsCallback.callCount).to.equal(1); + }); }); - describe('Opt out', function() { - before(function() { - coreStorage.setCookie('_pbjs_id_optout', '1', (new Date(Date.now() + 5000).toUTCString())); + describe('Opt out', function () { + before(function () { + coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '1', (new Date(Date.now() + 5000).toUTCString())); }); - beforeEach(function() { + beforeEach(function () { sinon.stub(utils, 'logInfo'); }); - afterEach(function() { + afterEach(function () { // removed cookie - coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); config.resetConfig(); }); - after(function() { - coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); - }); - - it('fails initialization if opt out cookie exists', function() { - setSubmoduleRegistry([pubCommonIdSubmodule]); + it('fails initialization if opt out cookie exists', function () { + 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]); + it('initializes if no opt out cookie exists', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); }); - describe('Handle variations of config values', function() { - beforeEach(function() { + describe('Handle variations of config values', function () { + beforeEach(function () { sinon.stub(utils, 'logInfo'); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); config.resetConfig(); }); - it('handles config with no usersync object', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + it('handles config with no usersync object', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -348,14 +516,14 @@ describe('User ID', function() { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); 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, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -366,7 +534,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, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -383,20 +551,36 @@ describe('User ID', function() { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); + }); + + it('handles config with name in different case', function () { + setSubmoduleRegistry([criteoIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + userIds: [{ + name: 'Criteo' + }] + } + }); + + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 9 configurations should result in 9 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + it('config with 23 configurations should result in 23 submodules add', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ + name: 'pubProvidedId' + }, { name: 'pubCommonId', value: {'pubcid': '11111'} }, { name: 'unifiedId', @@ -417,19 +601,52 @@ describe('User ID', function() { name: 'netId', storage: {name: 'netId', type: 'cookie'} }, { - name: 'sharedId', - storage: {name: 'sharedid', type: 'cookie'} + name: 'nextrollId' }, { name: 'intentIqId', - storage: { name: 'intentIqId', type: 'cookie' } + storage: {name: 'intentIqId', type: 'cookie'} + }, { + name: 'haloId', + storage: {name: 'haloId', type: 'cookie'} + }, { + name: 'zeotapIdPlus' + }, { + name: 'criteo' + }, { + name: 'mwOpenLinkId' + }, { + name: 'tapadId', + storage: {name: 'tapad_id', type: 'cookie'} + }, { + name: 'uid2' + }, { + name: 'admixerId', + storage: {name: 'admixerId', type: 'cookie'} + }, { + name: 'deepintentId', + 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'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 9 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + init(config); config.setConfig({ userSync: { @@ -444,7 +661,7 @@ describe('User ID', function() { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -459,7 +676,7 @@ describe('User ID', function() { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -472,975 +689,1869 @@ describe('User ID', function() { }); expect(auctionDelay).to.equal(0); }); - }); - - 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(events, 'on'); - sandbox.stub(coreStorage, 'getCookie'); + describe('auction and user sync delays', function () { + let sandbox; + let adUnits; + let mockIdCallback; + let auctionSpy; - // remove cookie - coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(global, 'setTimeout').returns(2); + sandbox.stub(global, 'clearTimeout'); + sandbox.stub(events, 'on'); + sandbox.stub(coreStorage, 'getCookie'); - adUnits = [getAdUnitMock()]; + // remove cookie + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); - 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}}; + 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}}; + } + 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.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'} + }] + } + }); - // check auction was delayed - // 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.setTimeout.calledOnce.should.equal(true); + global.clearTimeout.calledOnce.should.equal(false); + global.setTimeout.calledWith(sinon.match.func, 33); + auctionSpy.calledOnce.should.equal(false); - // if ids returned, should continue auction - mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); - auctionSpy.calledOnce.should.equal(true); + // check ids were fetched + mockIdCallback.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 - }); - done(); - }); + // if ids returned, should continue auction + mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); + auctionSpy.calledOnce.should.equal(true); - // no sync after auction ends - events.on.called.should.equal(false); - }); + // 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(); + }); - 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}); - - global.setTimeout.calledOnce.should.equal(false); - auctionSpy.calledOnce.should.equal(true); - mockIdCallback.calledOnce.should.equal(false); + 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'} + }] + } + }); - // no sync after auction ends - events.on.called.should.equal(false); - }); - }); + requestBidsHook(auctionSpy, {adUnits}); - describe('Request bids hook appends userId to bid objs in adapters', function() { - let adUnits; + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); + mockIdCallback.calledOnce.should.equal(false); - beforeEach(function() { - adUnits = [getAdUnitMock()]; + // no sync after auction ends + events.on.called.should.equal(false); + }); }); - it('test hook from pubcommonid cookie', function(done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); + describe('Request bids hook appends userId to bid objs in adapters', function () { + let adUnits; - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + beforeEach(function () { + adUnits = [getAdUnitMock()]; + }); - 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}] + it('test hook from pubcommonid cookie', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); + + 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}] + }); }); }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from pubcommonid config value object', function(done) { - setSubmoduleRegistry([pubCommonIdSubmodule]); - init(config); - config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + 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 html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); + + 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}] + }); + }); }); - }); - done(); - }, {adUnits}); - }); - - it('test hook from pubcommonid 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'])); + 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.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'}}] + 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 }); }); - }); - 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', ''); + done(); + }, {adUnits}); + }); - 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: 1}] + 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'])); + + 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('idl_env'); - localStorage.removeItem('idl_env_exp'); - done(); - }, {adUnits}); - }); + localStorage.removeItem('unifiedid_alt'); + localStorage.removeItem('unifiedid_alt_exp'); + done(); + }, {adUnits}); + }); - it('test hook from identityLink cookie', function(done) { - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); + it('test hook from amxId html5', (done) => { + // simulate existing localStorage values + localStorage.setItem('amxId', 'test_amxid_id'); + localStorage.setItem('amxId_exp', ''); + + 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, + }] + }); + }); + }); - setSubmoduleRegistry([identityLinkSubmodule]); - init(config); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); + // clear LS + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + done(); + }, {adUnits}); + }); - 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: 1}] + it('test hook from identityLink html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); + localStorage.setItem('idl_env_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}] + }); }); }); - }); - coreStorage.setCookie('idl_env', '', 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', ''); + localStorage.removeItem('idl_env'); + localStorage.removeItem('idl_env_exp'); + done(); + }, {adUnits}); + }); - 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: 1}] + it('test hook from identityLink cookie', function (done) { + coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); + + 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.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}] + }); }); }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + 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 criteoIdModule cookie', function (done) { + coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); + + 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.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('storage_bidid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + it('test hook from tapadIdModule cookie', function (done) { + coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); + + 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}); + }); - 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: 1}] + 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}] + }); }); }); - }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + 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 cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + + 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.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('kpuid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - 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' + it('test hook from Kinesso html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('kpuid', 'KINESSO_ID'); + localStorage.setItem('kpuid_exp', ''); + + 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}] + }); }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1, - ext: { - third: 'test_sharedId' - } - }] + }); + localStorage.removeItem('kpuid'); + localStorage.removeItem('kpuid_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())); + + 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}); - }); + coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); + 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('eidPermissions fun with bidders', function (done) { + coreStorage.setCookie('pubcid', 'test222', (new Date(Date.now() + 5000).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([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 + } + } + ] + } + }); + + 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'); + } }); }); - }); - 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('pubcid', '', EXPIRED_COOKIE_DATE); + getPrebidInternal().setEidPermissions = undefined; + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + it('eidPermissions fun without 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', + storage: { + type: 'cookie', + name: 'pubcid', + expires: 28 } - }] + } + ] + } + }); + + 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: 'pubcid.org', + uids: [ + { + id: 'test222', + atype: 1 + }] + }); }); }); - }); - 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())); + getPrebidInternal().setEidPermissions = undefined; + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + 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() { - 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' + 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: 'sharedid.org', - uids: [{ - id: 'test_sharedId', - atype: 1 - }] + }); + 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', ''); + + 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']} + }); }); }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + 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 cookie', function (done) { + coreStorage.setCookie('_li_pbid', JSON.stringify({ + 'unifiedId': 'random-cookie-identifier', + 'segments': ['123'] + }), (new Date(Date.now() + 100000).toUTCString())); + + 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}); + }); - 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: 1}], - ext: {segments: ['123']} + 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'])); + + 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}] + }); }); }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + 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())); + 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'])); + + 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}); + }); - setSubmoduleRegistry([liveIntentIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + 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'])); + + 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}); + }); - 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: 1}], - ext: {segments: ['123']} + 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'])); + + 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('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('intentIqId', '', 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 haloId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('haloId', JSON.stringify({'haloId': 'random-ls-identifier'})); + localStorage.setItem('haloId_exp', ''); + + setSubmoduleRegistry([haloIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['haloId', 'haloId', '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}] + }); + }); + }); + localStorage.removeItem('haloId'); + localStorage.removeItem('haloId_exp', ''); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([britepoolIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); + 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'])); + + 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}); + }); - 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: 1}] + 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'])); + + 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('britepoolid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('IDP', '', 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 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([netIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['netId', 'netId', 'cookie'])); + 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.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.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); }); }); - }); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook from intentIqId cookies', function(done) { - // simulate existing browser local storage values - coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'abcdefghijk'}), (new Date(Date.now() + 5000).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', '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}] + }); + }); + }); + localStorage.removeItem('admixerId'); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([intentIqIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); + it('test hook from admixerId cookie', function (done) { + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); + + 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}] + }); + }); + }); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - 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}] + 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', '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}] + }); }); }); - }); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, sharedId and netId 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('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); - init(config); - 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'])); - - 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'); - expect(bid.userId.id5id).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 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' + it('test hook from deepintentId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('deepintentId', 'testdeepintentId'); + localStorage.setItem('deepintentId_exp', ''); + + 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 intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - expect(bid.userIdAsEids.length).to.equal(8); }); - }); - 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); - done(); - }, {adUnits}); - }); + localStorage.removeItem('deepintentId'); + done(); + }, {adUnits}); + }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, sharedId and netId 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', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, haloId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid 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('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())); + 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()); + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + 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'], + ['haloId', 'haloId', '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'])); + + 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 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' + }); + expect(bid).to.have.deep.nested.property('userId.amxId'); + expect(bid.userId.amxId).to.equal('test_amxid_id'); + + // 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).to.have.deep.nested.property('userId.kpuid'); + expect(bid.userId.kpuid).to.equal('KINESSO_ID'); + + expect(bid.userIdAsEids.length).to.equal(17); + }); + }); + 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('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); + coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + done(); + }, {adUnits}); + }); - setSubmoduleRegistry([]); + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, dmdId, intentIqId, zeotapIdPlus, criteo, netId, haloId, 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('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())); + coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([]); + + // attaching before init + attachIdSystem(sharedIdSystemSubmodule); + + init(config); + + // attaching after init + attachIdSystem(unifiedIdSubmodule); + attachIdSystem(id5IdSubmodule); + attachIdSystem(identityLinkSubmodule); + attachIdSystem(britepoolIdSubmodule); + attachIdSystem(netIdSubmodule); + attachIdSystem(intentIqIdSubmodule); + attachIdSystem(zeotapIdPlusSubmodule); + attachIdSystem(haloIdSubmodule); + 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'], + ['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'], + ['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 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' + }); + + // 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(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('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); + coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); - // attaching before init - attachIdSystem(pubCommonIdSubmodule); + it('test hook from UID2 cookie', function (done) { + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + + 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}); + }); + 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('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())); + 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())); + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + 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: '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'} + }, { + name: 'amxId', storage: {name: 'amxId', type: 'html5'} + }, { + name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }] + } + }); - init(config); + // 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'}}; + } + }); - // attaching after init - attachIdSystem(unifiedIdSubmodule); - attachIdSystem(id5IdSubmodule); - attachIdSystem(identityLinkSubmodule); - attachIdSystem(britepoolIdSubmodule); - attachIdSystem(netIdSubmodule); - attachIdSystem(sharedIdSubmodule); - attachIdSystem(intentIqIdSubmodule); - - 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'])); - - 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'); - expect(bid.userId.id5id).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' + 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 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' + }); + + expect(bid).to.have.deep.nested.property('userId.amxId'); + expect(bid.userId.amxId).to.equal('test_amxid_id'); + + // 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(bid.userIdAsEids.length).to.equal(15); }); - // 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'); - expect(bid.userIdAsEids.length).to.equal(8); }); - }); - 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); - done(); - }, {adUnits}); + 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('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); + localStorage.removeItem('amxId'); + localStorage.removeItem('amxId_exp'); + coreStorage.setCookie('kpuid', 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())); + 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); + }); - setSubmoduleRegistry([]); - init(config); + 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; + }); - attachIdSystem(sharedIdSubmodule); + 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'}); + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + 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'); + }); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + it('unifiedid callback with url', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + expect(server.requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(server.requests[0].url).to.equal('/any/unifiedid/url'); + }); - requestBidsHook(function() { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.userIdAsEids).to.be.undefined; - }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); + it('unifiedid callback with partner', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {partner: 'rubicon'}); + + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + 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'); + }); }); - 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('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', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); - init(config); + 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'); + }); - 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: 'netId', storage: {name: 'netId', type: 'cookie'} - }, { - name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} - }, { - name: 'intentIqId', storage: { name: 'intentIqId', type: 'cookie' } - }, { - name: 'mockId', storage: {name: 'MOCKID', 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); }); + }); + + describe('Consent changes determine getId refreshes', function () { + let expStr; + let adUnits; - // Add new submodule named 'mockId' - attachIdSystem({ + const mockIdCookieName = 'MOCKID'; + let mockGetId = sinon.stub(); + let mockDecode = sinon.stub(); + let mockExtendId = sinon.stub(); + const mockIdSystem = { name: 'mockId', - decode: function(value) { - return { - 'mid': value['MOCKID'] - }; - }, - getId: function(params, storedId) { - if (storedId) return {}; - return {id: {'MOCKID': '1234'}}; + getId: mockGetId, + decode: mockDecode, + extendId: mockExtendId + }; + const userIdConfig = { + userSync: { + userIds: [{ + name: 'mockId', + storage: { + name: 'MOCKID', + type: 'cookie', + refreshInSeconds: 30 + } + }], + auctionDelay: 5 } - }); + }; - 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'); - expect(bid.userId.id5id).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'); - // 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'); - expect(bid.userIdAsEids.length).to.equal(8); + let cmpStub; + let testConsentData; + const consentConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + + 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 + adUnits = [getAdUnitMock()]; + init(config); + + // 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; + }; + + describe('TCF v1', function () { + testConsentData = { + gdprApplies: true, + consentData: 'xyz', + apiVersion: 1 + }; + + beforeEach(function () { + sharedBeforeFunction(); + + // init v1 consent management + window.__cmp = function () { + }; + delete window.__tcfapi; + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); }); + setConsentConfig(consentConfig); }); - 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('MOCKID', '', EXPIRED_COOKIE_DATE); - 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('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); - }); + afterEach(function () { + sharedAfterFunction(); + }); - 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); - }); + 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('pubcid callback with url', function() { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); - customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + }); - 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('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('unifiedid callback with url', function() { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - requestBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}); + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + }); - expect(server.requests).to.be.empty; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - expect(server.requests[0].url).to.equal('/any/unifiedid/url'); - }); + 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); - it('unifiedid callback with partner', function() { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {partner: 'rubicon'}); + setStoredConsentData(); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); - init(config); - config.setConfig(customCfg); - 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); + }); + + 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); + + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: 'abc', + apiVersion: testConsentData.apiVersion + }); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + }); + + 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); - 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'); + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: testConsentData.consentData, + apiVersion: testConsentData.apiVersion + }); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + sinon.assert.notCalled(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.calledOnce(mockExtendId); + }); + }); + + describe('findRootDomain', function () { + let sandbox; + + 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 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 c9d826d8dc8..b1cfa606d84 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,105 +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': 'vdo.ai', - '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': 'vdo.ai', - 'params': { - placementId: 'testPlacementId' - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - } - ]; - - 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)); - }); - }); -}); +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/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js new file mode 100644 index 00000000000..10054b5674c --- /dev/null +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -0,0 +1,204 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; + +describe('Verizon Media ID 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(verizonMediaIdSubmodule.name).to.equal('verizonMediaId'); + }); + + it('should have the correct TCFv2 Vendor ID declared', () => { + expect(verizonMediaIdSubmodule.gvlid).to.equal(25); + }); + + describe('getId()', () => { + let ajaxStub; + let getAjaxFnStub; + let consentData; + beforeEach(() => { + ajaxStub = sinon.stub(); + getAjaxFnStub = sinon.stub(verizonMediaIdSubmodule, '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 = verizonMediaIdSubmodule.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 = utils.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 = utils.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 = utils.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 = utils.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 = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + }); + + describe('decode()', () => { + const VALID_API_RESPONSES = [{ + key: 'vmiud', + expected: '1234', + payload: { + vmuid: '1234' + } + }, + { + key: 'connectid', + expected: '4567', + payload: { + connectid: '4567' + } + }, + { + key: 'both', + expected: '4567', + payload: { + vmuid: '1234', + connectid: '4567' + } + }]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the connectid for a payload with ${responseData.key} key(s)', () => { + expect(verizonMediaIdSubmodule.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(verizonMediaIdSubmodule.decode(response)).to.be.undefined; + }); + }); + }); +}); 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/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index b8ab83a95ae..35f510fd6ee 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,7 +1,22 @@ import { expect } from 'chai'; -import { spec as adapter, SUPPORTED_ID_SYSTEMS, createDomain } from 'modules/vidazooBidAdapter.js'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, + getNextDealId, + getVidazooSessionId, +} from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; const SUB_DOMAIN = 'openrtb'; @@ -45,6 +60,7 @@ const SERVER_RESPONSE = { 'exp': 30, 'width': 300, 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], 'cookies': [{ 'src': 'https://sync.com', 'type': 'iframe' @@ -64,10 +80,6 @@ const REQUEST = { } }; -const SYNC_OPTIONS = { - 'pixelEnabled': true -}; - describe('VidazooBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { @@ -129,6 +141,7 @@ describe('VidazooBidAdapter', function () { }); it('should build request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.referer); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -146,6 +159,8 @@ describe('VidazooBidAdapter', function () { adUnitCode: 'div-gpt-ad-12345-0', publisherId: '59ac17c192832d0011283fe3', dealId: 1, + sessionId: '', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, bidderVersion: adapter.version, prebidVersion: version, res: `${window.top.screen.width}x${window.top.screen.height}`, @@ -165,7 +180,7 @@ describe('VidazooBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://static.cootlogix.com/basev/sync/user_sync.html' + url: 'https://prebid.cootlogix.com/api/sync/iframe/?gdpr=0&gdpr_consent=&us_privacy=' }]); }); @@ -173,7 +188,7 @@ describe('VidazooBidAdapter', function () { const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.com', + 'url': 'https://prebid.cootlogix.com/api/sync/image/?gdpr=0&gdpr_consent=&us_privacy=', 'type': 'image' }]); }) @@ -207,7 +222,10 @@ describe('VidazooBidAdapter', function () { currency: 'USD', netRevenue: true, ttl: 30, - ad: '' + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } }); }); @@ -220,7 +238,7 @@ describe('VidazooBidAdapter', function () { }); }); - describe(`user id system`, function () { + describe('user id system', function () { Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); @@ -229,6 +247,8 @@ describe('VidazooBidAdapter', function () { switch (idSystemProvider) { case 'digitrustid': return { data: { id: id } }; case 'lipb': return { lipbid: id }; + case 'parrableId': return { eid: id }; + case 'id5id': return { uid: id }; default: return id; } })(); @@ -243,4 +263,114 @@ describe('VidazooBidAdapter', function () { }); }); }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('vidazoo session id', function () { + it('should get undefined vidazoo session id', function () { + const sessionId = getVidazooSessionId(); + expect(sessionId).to.be.empty; + }); + + it('should get vidazoo session id from storage', function () { + const vidSid = '1234-5678'; + window.localStorage.setItem('vidSid', vidSid); + const sessionId = getVidazooSessionId(); + expect(sessionId).to.be.equal(vidSid); + }); + }); + + describe('deal id', function () { + const key = 'myDealKey'; + + it('should get the next deal id', function () { + const dealId = getNextDealId(key); + const nextDealId = getNextDealId(key); + expect(dealId).to.be.equal(1); + expect(nextDealId).to.be.equal(2); + }); + + it('should get the first deal id on expiration', function (done) { + setTimeout(function () { + const dealId = getNextDealId(key, 100); + expect(dealId).to.be.equal(1); + done(); + }, 200); + }); + }); + + describe('unique deal id', function () { + const key = 'myKey'; + let uniqueDealId; + uniqueDealId = getUniqueDealId(key); + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function () { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + }); + }); + + describe('storage utils', function () { + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const { value, created } = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); }); 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..b8e41829031 --- /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 + 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 + '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..37452914e79 --- /dev/null +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -0,0 +1,210 @@ +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/`; + +describe('vidoomyBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(() => { + bid = { + 'bidder': 'vidoomy', + 'params': { + pid: '123123', + id: '123123' + }, + '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 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.include(ENDPOINT); + expect(request[1].url).to.include(ENDPOINT); + }); + + it('only accepts first width and height sizes', function () { + expect(request[0].url).to.include('w=300'); + expect(request[0].url).to.include('h=250'); + expect(request[0].url).to.not.include('w=200'); + expect(request[0].url).to.not.include('h=100'); + expect(request[1].url).to.include('w=400'); + expect(request[1].url).to.include('h=225'); + }); + + it('should send id and pid parameters', function () { + expect(request[0].url).to.include('id=123123'); + expect(request[0].url).to.include('pid=123123'); + expect(request[1].url).to.include('id=456456'); + expect(request[1].url).to.include('pid=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': '